Setting up your file places
A hands-on guide: in about ten minutes your application says, once, where its files live — and from then on every file operation on CkFile knows the folder behind a FilePlace, the extensions it takes and the most bytes a file may have. As shipped nothing is set, on purpose: the library cannot touch a disk until you have said where, and this page is the saying. You type, you run, you see the answer change.
You need the library referenced in a project first — two minutes on Getting started if you haven't done it.
Step 1 — a project to play in
dotnet new console -n PlacesTutorial
cd PlacesTutorial
dotnet add reference ../SafeCoder.CommonKit/SafeCoder.CommonKit.csproj
Open Program.cs, delete what's in it, and paste this:
using SafeCoder.CommonKit;
using SafeCoder.CommonKit.Enums;
using SafeCoder.CommonKit.Exceptions;
try
{
Console.WriteLine(CkFile.FullPath(FilePlace.Documents, "check.pdf", throwIfInvalid: true));
}
catch (CkFileException refused)
{
Console.WriteLine(refused.Reason);
}
Run it:
no folder is set for Documents
That is the library as shipped. FullPath is the cheapest question you can ask a place —
it reads nothing and writes nothing, it only answers where a file would go — and with
throwIfInvalid: true it says why it cannot. Keep this program around: it is the check
you run after every change below.
Step 2 — one root, in appsettings.json
Make a folder for the files, anywhere outside the application: D:\Files on a Windows
server, /var/app/files on Linux, /Users/you/Files on a Mac. It has to exist already.
Then add a file named appsettings.json to the project, next to Program.cs:
{
"CommonKit": {
"Files": {
"Root": "D:\\Files"
}
}
}
Two things about that path. The backslash is doubled because JSON needs it so — D:/Files,
with forward slashes, is the same folder to Windows and needs no doubling. And it is
absolute: a relative path is refused, since nobody can say what it is relative to.
Where the library looks. Beside the application — in the folder the running program is
in, which is the build output, bin/Debug/net10.0, not the project folder. A web or worker
project copies appsettings.json there on every build by itself; a console project does
not, so tell it to, in the .csproj:
<ItemGroup>
<None Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="appsettings.*.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
The second line is for the environment files of Step 4. dotnet publish carries them the
same way, so the published folder has what the library needs.
Run again:
D:\Files\Documents\check.pdf
Documents is the subfolder named after the place. It does not exist yet, and need not:
the first write makes it. Every place works like that, from the one root — D:\Files\Images,
D:\Files\Audio, D:\Files\Imports — the ten names are on the
FilePlace page. Write something and watch it appear:
byte[] csv = System.Text.Encoding.UTF8.GetBytes("id,name\n1,Mary\n");
Console.WriteLine(CkFile.Write(FilePlace.Imports, "feed.csv", csv)); // -> True
Console.WriteLine(CkFile.FullPath(FilePlace.Imports, "feed.csv")); // -> D:\Files\Imports\feed.csv
D:\Files\Imports is there now, with feed.csv in it. The write passed because Imports
takes a csv and the content is text; what a place checks before it takes a file is on the
CkFile page, under Your file places, set once.
Step 3 — a rule or two per place
Beside the root, a place can have an object of its own with up to three keys — Root,
for a place that lives somewhere else; MaxBytes; Extensions — and beside the places,
Blocked:
{
"CommonKit": {
"Files": {
"Root": "D:\\Files",
"Documents": { "MaxBytes": 10485760, "Extensions": ["pdf", "docx", "csv"] },
"Images": { "MaxBytes": 4194304 },
"Archives": { "Root": "E:\\Archive" },
"Blocked": ["rar", "7z"]
}
}
}
Every key stands alone: Images names a limit and keeps the library's own extensions; Archives has a folder of its own and keeps the default limit and list; a place not named at all — Videos, Audio — keeps everything. Add these lines to the program to see the rules in force:
Console.WriteLine(CkFile.MaxBytes(FilePlace.Documents)); // -> 10485760
Console.WriteLine(string.Join(", ", CkFile.AllowedExtensions(FilePlace.Documents))); // -> pdf, docx, csv
Console.WriteLine(string.Join(", ", CkFile.AllowedExtensions(FilePlace.Archives))); // -> zip, gz, tar
Console.WriteLine(CkFile.FullPath(FilePlace.Archives, "2025.zip")); // -> E:\Archive\2025.zip
Archives lost rar and 7z: Blocked adds to the library's own blocked list, and a
blocked extension is taken nowhere, whatever a place lists. The list only grows — nothing
you write can make the library take a php or an exe. Three small rules for the values:
MaxBytes is a number of bytes (10485760, never "10 MB"), and zero or anything
unreadable means "not given"; an extension is written without its dot, and casing does not
matter; and a folder of its own must exist, like the root, since only the subfolders under
the root are made for you.
Step 4 — a different root per environment
Your machine and the server keep their files in different folders. Add
appsettings.Development.json next to appsettings.json, with only what differs:
{
"CommonKit": {
"Files": {
"Root": "C:\\Dev\\Files"
}
}
}
Which second file is read depends on the environment's name: ASPNETCORE_ENVIRONMENT if
that variable is set, DOTNET_ENVIRONMENT otherwise, and Production when neither is.
The web and worker templates set it to Development in Properties/launchSettings.json,
which Visual Studio and dotnet run apply; a console app sets nothing, so set it by hand
to try this step:
DOTNET_ENVIRONMENT=Development dotnet run # bash, zsh
$env:DOTNET_ENVIRONMENT = "Development"; dotnet run # PowerShell
The answer is now C:\Dev\Files\Documents\check.pdf, and the Documents rules from
appsettings.json still hold: a later file overrides an earlier one key by key, never
whole. On the server nothing names the environment, so it is Production, and
appsettings.Production.json is read if you have one.
Step 5 — a variable over everything
The same keys, with __ where the file has a level:
CommonKit__Files__Root=D:\Files
CommonKit__Files__Documents__MaxBytes=5242880
CommonKit__Files__Documents__Extensions=pdf,docx
CommonKit__Files__Archives__Root=E:\Archive
CommonKit__Files__Blocked=rar,7z
A variable overrides both files, key by key. That is the spelling the .NET host itself reads, so a Dockerfile, a systemd unit, an IIS site or an Azure App Service setting that already speaks it needs nothing new.
A root kept somewhere else — in user secrets, a vault, a database — reaches the library
the same way. It reads only the two files and the process's variables, and reads them
once, at the first file operation; so hand the value to the process before that, at the
top of Program.cs:
var builder = WebApplication.CreateBuilder(args);
// from wherever the host keeps it — user secrets in development, a vault in production
Environment.SetEnvironmentVariable("CommonKit__Files__Root", builder.Configuration["Storage:FilesRoot"]);
That sets a variable for this process alone, nothing outside it changes, and the library finds it like any other. A value set after the first file operation is never seen.
Step 6 — in code, when you'd rather not have a file
The library travels as source, so the first door is CkSettings.cs in your copy of it:
the constant DefaultFileRoot for the root, and three methods — FileRoot, FileMaxBytes,
FileExtensions — each a switch over the places, answering null for every one as
shipped. Fill in what you want:
public const string? DefaultFileRoot = @"D:\Files";
public static string? FileRoot(FilePlace place)
{
return place switch
{
FilePlace.Archives => @"E:\Archive",
_ => null
};
}
public static long? FileMaxBytes(FilePlace place)
{
return place switch
{
FilePlace.Documents => 10_485_760,
_ => null
};
}
The constant takes a literal and nothing else; the methods may answer anything — a literal,
Environment.GetEnvironmentVariable("STORAGE_ROOT"), a path built with Path.Combine.
The files and the variables override these key by key, so what you write here is the
floor, not a lock: a fixed value is right for a server that never moves, and the file is
right for everything else.
When a place still says no
Every refusal names the place and says why. These are the ones a setting causes, as
FullPath reports them with throwIfInvalid: true:
| The reason | What to look at |
|---|---|
no folder is set for Documents |
No root and no folder reached the library. Is appsettings.json in the output folder? Is the section spelled CommonKit → Files? |
the folder set for Documents is not an absolute path |
files or ..\Files: write the whole path. |
the folder set for Documents is a root folder |
D:\ or /: a place is a folder of its own, never a whole drive. |
the folder set for Documents is the application's own folder, or one above it |
The root holds the application — bin, the site, the project — so ../web.config would be in bounds. Move the files out. |
the folder set for Documents does not exist |
The root, or the place's own folder, is not there yet. Make it; the library makes only the subfolders under the root. |
Two facts behind the table. The settings are read once, at the first file operation: a change to the file or a variable needs a restart to be seen. And a settings file the library cannot parse — a missing comma, a stray character — changes nothing and says nothing, because the read happens inside your first file operation, where nothing may throw: the places stay as the code says, which as shipped is nowhere. So when a place says "no folder is set" after you set one, the file is the first thing to check, and the program from Step 1 is how.
Where next
- Taking an upload — the handler, now that the places are set: the name cleaned, the file judged, a CSV read as data.
- FilePlace — the ten places and what each takes by default.
- Your file places, set once — the reference: every key, the defaults, and the rules that stand whatever you set.
- How it works — the five rules, and which reader to call.