StageKit 0.1.4

dotnet add package StageKit --version 0.1.4
                    
NuGet\Install-Package StageKit -Version 0.1.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StageKit" Version="0.1.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StageKit" Version="0.1.4" />
                    
Directory.Packages.props
<PackageReference Include="StageKit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StageKit --version 0.1.4
                    
#r "nuget: StageKit, 0.1.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StageKit@0.1.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StageKit&version=0.1.4
                    
Install as a Cake Addin
#tool nuget:?package=StageKit&version=0.1.4
                    
Install as a Cake Tool

StageKit

Logo

License GitHub repo size Code size Nuget GitHub Sponsors

StageKit is a lightweight .NET application infrastructure library for JSON settings files, observable settings objects, crash report capture, application runtime metadata, and unhandled exception handling.

Features

  • Singleton JSON settings files with lazy load, manual save, AutoSave, and debounced save support
  • Settings schema versioning with migration and validation/repair hooks
  • AutoSave suspension and batch update scopes
  • Observable settings base classes powered by CommunityToolkit.Mvvm
  • Collection-backed settings files using ObservableList<T> with ItemsView for synchronized binding
  • Save hooks through BeforeSave() and AfterSave()
  • Pending debounce tracking with timeout-aware wait support
  • Single-instance process guard based on a named mutex
  • Atomic file writes, profile backup/restore, support bundle export, and retention helpers
  • First-run and onboarding state persistence
  • Serializable crash reports with exception chains, stack traces, runtime information, and process stats
  • AppDomain and task scheduler unhandled exception helpers
  • Panic-save support for registered ISavable settings before forced process exit
  • Configurable profile, config, and log paths
  • Small application "birthday" helpers for version/about screens

Install

dotnet add package StageKit

Requirements

  • .NET 8 or newer
  • C# latest language version

Quick Start

Configure StageKit during application startup:

using StageKit;

ApplicationKit.ApplicationName = "MyApp";
ApplicationKit.ApplicationArgs = args;
ApplicationKit.Logger = logger;
ApplicationKit.UiFrameworkInfo = $"Avalonia {typeof(AvaloniaObject).Assembly.GetName().Version!.ToString(3)}";
ApplicationKit.Birth = DateTime.SpecifyKind(new DateTime(2026, 1, 1), DateTimeKind.Utc);

UnhandledExceptions.RegisterAppDomainUnhandledException();
UnhandledExceptions.RegisterTaskSchedulerUnobservedTaskException();

CrashReportsFile.IsEnabled = true;

Settings Files

Create a root settings file by inheriting from RootSettingsFile<T>:

using StageKit;

public partial class AppSettings : RootSettingsFile<AppSettings>
{
    [ObservableProperty]
    public partial string Theme { get; set; } = "System";

    [ObservableProperty]
    public partial string ThemeColor { get; set; } = "Blue";

    [ObservableProperty]
    public partial bool EnableCrashReporting { get; set; } = true;

    [ObservableProperty]
    public partial long LastRunTimestamp { get; set; }

    public AppSettings()
    {
        FileName = "appsettings.json";
        AutoSave = true;
    }
}

Enable AutoSave when property changes should save automatically:

var settings = AppSettings.Instance;
settings.AutoSave = true;
settings.Theme = "Dark";

AutoSave defaults to false. It starts reacting only after the settings object is loaded, so property changes caused by JSON hydration or OnLoaded(...) do not rewrite the file during startup.

Keep AutoSave disabled when you prefer manual control:

AppSettings.Instance.AutoSave = false;

AppSettings.SaveInstance();

Use debounced save APIs when you want to batch rapid changes:

settings.DebouncedSave();

var saved = await settings.WaitForDebouncedSaveAsync(
    TimeSpan.FromSeconds(5),
    cancellationToken);

if (!saved)
{
    // timeout elapsed while the save was still pending
}

Save() cancels any pending debounced save before writing. SaveCount tracks successful saves for the current in-memory instance and is ignored in JSON. CancelDebouncedSave() cancels a scheduled save without writing.

Suspend AutoSave while applying several related changes:

settings.BatchUpdate(() =>
{
    settings.Theme = "Dark";
    settings.ThemeColor = "Violet";
});

using (settings.SuspendAutoSave(saveOnDispose: false))
{
    settings.Theme = "Light";
}

BatchUpdate(...) and SuspendAutoSave(...) still mark the file dirty while AutoSave is suspended. When the outermost scope exits, StageKit schedules one debounced save if changes happened and saving on dispose is enabled.

By default, settings are stored under:

ApplicationKit.ProfilePath

Override DirectoryPath or set ApplicationKit.ProfilePath to customize storage.

Use schema versioning and validation hooks for app-owned settings evolution:

public sealed class AppSettings : RootSettingsFile<AppSettings>
{
    protected override int CurrentSettingsVersion => 2;

    public string Theme { get; set; } = "System";

    protected override void MigrateSettings(SettingsMigrationContext context)
    {
        if (context.FromVersion < 2)
        {
            Theme = "System";
        }
    }

    protected override void ValidateSettings(SettingsValidationContext context)
    {
        if (!string.IsNullOrWhiteSpace(Theme)) return;

        Theme = "System";
        context.MarkChanged("Theme was empty.");
    }
}

SettingsVersion is serialized with each settings file. Older files are migrated to CurrentSettingsVersion and kept dirty so the upgraded schema can be persisted. If a file was written by a newer app version, StageKit renames it to <file>.unsupported-version-<timestampUtc> and falls back to a fresh instance.

If a settings file fails to deserialize on load (corrupt JSON), StageKit renames it to <file>.corrupt-<timestampUtc> and falls back to a fresh instance. Original data is preserved on disk for inspection or recovery.

Collection Settings

Use RootCollectionFile<T, TO> when a settings file is mainly a list:

using StageKit;

public sealed class RecentFiles : RootCollectionFile<RecentFiles, string>
{
    public RecentFiles()
    {
        FileName = "recent-files.json";
        TrimCollectionWhenExceeding = 20;
        TrimCollectionSide = CollectionSide.Head;
    }
}
RecentFiles.Instance.Add(@"C:\work\project.txt");
RecentFiles.SaveInstance();

RootCollectionFile<T, TO> exposes Items as an ObservableList<TO> and ItemsView as a synchronized view for UI binding. Set TrackItemsWithChangeNotification = true in the constructor when collection items implement INotifyPropertyChanged and item property changes should mark the file dirty and trigger AutoSave; keep it disabled for immutable items or very large collections.

Observable Objects

SubSettings is based on CommunityToolkit.Mvvm.ComponentModel.ObservableObject:

using StageKit;

public sealed class ViewState : SubSettings
{
    private bool _isBusy;

    public bool IsBusy
    {
        get => _isBusy;
        set => SetProperty(ref _isBusy, value);
    }
}

Classes deriving from SubSettings, RootSettingsFile<T>, or RootCollectionFile<T, TO> can use CommunityToolkit's [ObservableProperty] generator:

using CommunityToolkit.Mvvm.ComponentModel;
using StageKit;

public sealed partial class AppSettings : RootSettingsFile<AppSettings>
{
    public AppSettings()
    {
        FileName = "appsettings.json";
    }

    [ObservableProperty]
    private string _theme = "System";
}

StageKit references CommunityToolkit.Mvvm, but if your app uses generator attributes such as [ObservableProperty], reference CommunityToolkit.Mvvm directly in that app too so the analyzer/source generator runs in the consuming project.

Crash Reports

Create crash reports directly:

try
{
    RunApplication();
}
catch (Exception ex)
{
    var report = new CrashReport(ex, "Startup");
    Console.WriteLine(report.FormattedMessage);
}

StageKit can also capture unhandled exceptions:

UnhandledExceptions.HandleCrashReport = report =>
{
    Console.WriteLine(report.FormattedMessage);
    return true;
};

If CrashReportsFile.IsEnabled is true, fatal unhandled exceptions are added to CrashReportsFile.Instance.

Register settings that should be saved before StageKit forces process exit after a fatal exception:

UnhandledExceptions.SettingsFilesToSaveBeforeCrash.Add(AppSettings.Instance);

SettingsFilesToSaveBeforeCrash is a HashSet<StageKit.Interfaces.ISavable>, so any type implementing ISavable can participate.

Single Instance Guard

Use ApplicationInstanceGuard when an app should allow only one primary process:

using var guard = ApplicationInstanceGuard.Acquire("MyCompany.MyApp");

if (guard.IsSecondary)
{
    return;
}

RunApplication();

The guard uses a named mutex. Because .NET mutex ownership is thread-affine, dispose the guard on the same thread that acquired it. It does not forward activation arguments yet, but the API is shaped so named-pipe activation forwarding can be added later.

If your app also launches a crash-report viewer with ApplicationKit.CrashReportFlag, check the crash-report mode before blocking secondary instances, or use a different instance name for the viewer process.

Storage Utilities

Use SafeFile when application code needs an atomic write outside RootSettingsFile<T>:

SafeFile.WriteAllText(path, json);
SafeFile.Write(path, stream => JsonSerializer.Serialize(stream, model));

Create and restore profile backups:

var backupPath = ApplicationBackup.Create();
ApplicationBackup.Restore(backupPath);

var asyncBackupPath = await ApplicationBackup.CreateAsync();
await ApplicationBackup.RestoreAsync(asyncBackupPath);

Export a diagnostics bundle for support:

var bundlePath = SupportBundleExporter.Export(new SupportBundleOptions
{
    Notes = "User reported startup failure"
});

var asyncBundlePath = await SupportBundleExporter.ExportAsync();

Apply retention to logs and crash reports:

ApplicationRetention.LogRetentionPolicy.MaxAge = TimeSpan.FromDays(14);
ApplicationRetention.LogRetentionPolicy.MaxFiles = 50;
ApplicationRetention.ApplyLogRetention();

ApplicationRetention.ApplyCrashReportRetention(maxCrashReports: 25, maxAge: TimeSpan.FromDays(30));

Track first-run and onboarding state:

var state = OnboardingStateFile.Instance;
state.RecordLaunch();

if (state.IsFirstRun)
{
    ShowOnboarding();
    state.CompleteOnboarding("v1");
}

Ignoring Known Safe Exceptions

Ignore by exception type:

UnhandledExceptions.IgnoredExceptionList.Add(typeof(OperationCanceledException));

Ignore by message fragment:

UnhandledExceptions.IgnoredExceptionMessages.Add("known benign message");

Avalonia DBus noise can be ignored with:

UnhandledExceptions.IgnoreAvaloniaSafeExceptions();

Application Birthday Helpers

ApplicationKit.Birth = DateTime.SpecifyKind(new DateTime(2026, 1, 1), DateTimeKind.Utc);

Console.WriteLine(ApplicationKit.YearsOld);
Console.WriteLine(ApplicationKit.AgeShortStr);
Console.WriteLine(ApplicationKit.IsBirthday);

Runtime duration since library initialization is available through:

Console.WriteLine(ApplicationKit.RuntimeElapsed);

Demo

See StageKit.Demo/Program.cs for a runnable console demo covering startup configuration, Serilog integration, AutoSave settings, collection settings, panic-save registration, crash report launch handling, and debounced save waiting.

Run it with:

dotnet run --project StageKit.Demo/StageKit.Demo.csproj

Development

Restore, build, and test:

dotnet restore
dotnet build
dotnet test

Security

Please report vulnerabilities privately. See SECURITY.md.

Contributing

Contributions are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.

License

StageKit is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.4 34 5/7/2026
0.1.3 85 5/3/2026
0.1.2 87 5/3/2026
0.1.1 87 5/3/2026
0.1.0 102 5/2/2026