Lockjaw.Client 2.0.2.33

There is a newer version of this package available.
See the version list below for details.
dotnet add package Lockjaw.Client --version 2.0.2.33
                    
NuGet\Install-Package Lockjaw.Client -Version 2.0.2.33
                    
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="Lockjaw.Client" Version="2.0.2.33" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lockjaw.Client" Version="2.0.2.33" />
                    
Directory.Packages.props
<PackageReference Include="Lockjaw.Client" />
                    
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 Lockjaw.Client --version 2.0.2.33
                    
#r "nuget: Lockjaw.Client, 2.0.2.33"
                    
#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 Lockjaw.Client@2.0.2.33
                    
#: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=Lockjaw.Client&version=2.0.2.33
                    
Install as a Cake Addin
#tool nuget:?package=Lockjaw.Client&version=2.0.2.33
                    
Install as a Cake Tool

Lockjaw.Client

Read the environment currently selected in the Lockjaw app from any .NET application, and react the moment it changes.

Lockjaw keeps every environment's values in one configuration — keys prefixed Development_, Production_, or whatever else you declare — and lets you choose, per setting and while your app is running, which one each resolves to. This package is the reading half.

using Lockjaw.Client;

var apiUrl = LockjawClient.Default.Get("ApiUrl");
var db     = LockjawClient.Default.GetConnectionString("Main");

Ask for the key without its prefix; you get whichever environment is currently selected. No configuration on your side — the client and the app find each other by instance name.

Finding your instance

One Lockjaw manages several applications, each published under its own instance name. Name the one you want:

var client = new LockjawClient(new LockjawClientOptions { InstanceName = "OrderService" });

Or let the client find it. Lockjaw publishes the application names each instance serves, so a client can match its own and needs no configuration at all:

// In OrderService.exe — no instance name anywhere.
var client = new LockjawClient(new LockjawClientOptions { AutoDiscoverInstance = true });

Console.WriteLine(client.InstanceName);   // "OrderService", resolved at construction

ApplicationName defaults to the entry assembly's name; set it explicitly if your executable is named differently from the instance's target. Discovery is off by default and never overrides an explicit choice: an explicit SnapshotPath or a non-default InstanceName wins, and if nothing matches the client falls back to default.

To see what is available:

foreach (var instance in SnapshotStore.EnumerateInstances())
    Console.WriteLine($"{instance.Instance}: {string.Join(", ", instance.Applications)}");

netstandard2.0, so it works from .NET Framework 4.6.1+ and .NET Core / .NET 5+.

Typed reads

var timeout = LockjawClient.Default.Get("TimeoutSeconds", 30);   // parsed, with a fallback
var url     = LockjawClient.Default.GetRequired("ApiUrl");       // throws if missing

Colon-delimited paths

Nested JSON flattens into colon paths, the same convention .NET configuration uses, so ask for a nested value by its path:

var level = LockjawClient.Default.Get("Logging:Level");
var host  = LockjawClient.Default.Get("Hosts:0");            // arrays take their index

The environment prefix can sit on any segment of the path, which is what lets a single leaf vary while its siblings stay shared:

{
  // The whole subtree varies:
  "Development_Logging": { "Level": "Debug", "Console": { "Enabled": true } },
  "Production_Logging":  { "Level": "Warning", "Console": { "Enabled": false } },

  // …or just one leaf, with its siblings shared:
  "Telemetry": {
    "Development_Endpoint": "http://localhost:4317",
    "Production_Endpoint":  "https://otel.contoso.com",
    "SampleRate": "0.25"
  }
}

Both forms give you Logging:Level, Logging:Console:Enabled and Telemetry:Endpoint — ask for the path with the prefix removed, exactly as you would for a flat key. Connection strings nest the same way, and a flat key that simply contains a colon (Development_Api:Url in app.config) works too.

Top-level pairs — individual keys that sit beside the AppSettings and ConnectionStrings sections rather than inside them — are read as app settings, so Get("SomeTopLevelKey") returns them directly.

As an IConfiguration

LockjawClient is a Microsoft.Extensions.Configuration.IConfiguration, so its resolved values are readable with the standard path syntax and bindable with IOptions:

IConfiguration config = LockjawClient.Default;

var db  = config["ConnectionStrings:Main"];        // == config.GetConnectionString("Main")
var url  = config["ApiUrl"];                        // an AppSettings value, prefix already resolved
var flag = config["FeatureEnabled"];               // a top-level pair

App settings (including the top-level pairs and nested keys) appear at the root by their base key, and connection strings under the conventional ConnectionStrings section. The environment prefix is already stripped, so ConnectionStrings:Main reads whichever of Development_Main / Production_Main is currently active — switch the environment in the Lockjaw app and the value follows. A change also fires IConfiguration.GetReloadToken(), so IOptionsMonitor and reloadOnChange bindings refresh.

Add it to a host builder's configuration pipeline with AddLockjaw:

builder.Configuration.AddLockjaw();                // the Default client
// or a specific one:
builder.Configuration.AddLockjaw(myClient);

Because Lockjaw is layered last, its live, environment-switched values take precedence over the JSON and environment-variable sources added before it.

Reacting to changes

Changes are picked up live. The app pushes each change to the client over a SIPC semaphore mesh the moment you switch an environment or apply one to everything — the client does not poll or watch a file, so delivery is prompt and survives a locked-down machine where file-change notifications are unreliable (a redirected profile, a VDI/roaming setup, or aggressive endpoint protection). If the app is not running yet, the client keeps asking for the current snapshot and picks up the first change once it appears.

The event arrives on a background thread, so a WPF or WinForms handler must reach the UI thread first — SubscribeOnUiThread does that for you when called from the UI thread, and returns a token you dispose to unsubscribe:

_subscription = LockjawClient.Default.SubscribeOnUiThread((s, e) =>
{
    ApiUrlText.Text = e.Value;     // already on the dispatcher
});

Under the hood it captures the UI thread's SynchronizationContext — a DispatcherSynchronizationContext on WPF — so this package needs no WPF reference of its own. For a context you hold yourself, use SubscribeOn(context, handler). Off the UI thread, subscribe to SettingChanged directly.

When the Lockjaw app isn't running

  • Values stay available once the app has run once — the client reads the last published values.
  • If nothing was ever published, the client falls back to its own app.config, web.config or appsettings.json, read with the same prefix convention. So an app that references Lockjaw.Client still works standalone.
  • Published values always win, per key; the fallback only fills gaps.
  • SourceOf(key) tells you which it was: Published, LocalConfig, or None.
switch (LockjawClient.Default.SourceOf("ApiUrl"))
{
    case LockjawValueSource.Published:   /* the user picked this in the app */ break;
    case LockjawValueSource.LocalConfig: /* app not running; from my own config */ break;
    case LockjawValueSource.None:        /* nobody has this key */ break;
}

Your own fallback config

Ship the settings in your application's own appsettings.json and it keeps working on a machine where Lockjaw has never run. Same prefix convention:

{
  // No "Lockjaw" section — which environments exist is the Lockjaw app's business.
  "AppSettings": {
    "Local_ApiUrl":       "http://localhost:5000/api",
    "Development_ApiUrl": "https://localhost:5001/api",
    "Production_ApiUrl":  "https://api.contoso.com",

    // Partial coverage is fine: with no Local value, Local resolves to Development.
    "Development_LogLevel": "Debug",
    "Production_LogLevel":  "Warning",

    // Unprefixed keys are served too, so ordinary settings keep working.
    "SupportEmail": "support@contoso.com"
  },

  "ConnectionStrings": {
    "Local_Main":       "Server=(localdb)\\MSSQLLocalDB;Database=Contoso_Local",
    "Development_Main": "Server=(localdb)\\MSSQLLocalDB;Database=Contoso_Dev",
    "Production_Main":  "Server=sql-prod-01;Database=Contoso"
  }
}

Or in app.config / web.config:

<configuration>
  <appSettings>
    <add key="Local_ApiUrl"       value="http://localhost:5000/api" />
    <add key="Development_ApiUrl" value="https://localhost:5001/api" />
    <add key="Production_ApiUrl"  value="https://api.contoso.com" />

    
    <add key="SupportEmail" value="support@contoso.com" />
  </appSettings>

  <connectionStrings>
    <add name="Development_Main"
         connectionString="Server=(localdb)\MSSQLLocalDB;Database=Contoso_Dev" />
    <add name="Production_Main"
         connectionString="Server=sql-prod-01;Database=Contoso" />
  </connectionStrings>
</configuration>

Three things differ from the Lockjaw app's own configuration:

  • No Lockjaw section. Which environments exist is the app's business. The client needs the list only when its own keys use prefixes beyond Development_ / Production_.
  • Unprefixed keys are served, where the Lockjaw window hides them — so your ordinary settings can live in the same file, untouched.
  • Prefixed keys resolve to FallbackEnvironment, which defaults to Development, so a machine without Lockjaw never quietly starts on production.

Anything beyond the defaults is set in code rather than in the file:

using var client = new LockjawClient(new LockjawClientOptions
{
    // Only needed for prefixes beyond the built-in pair.
    Environments = new List<EnvironmentDefinition>
    {
        new EnvironmentDefinition("Local",       "Local_"),
        new EnvironmentDefinition("Development", "Development_"),
        new EnvironmentDefinition("Production",  "Production_")
    },

    FallbackEnvironment       = "Local",  // which one the fallback picks
    IncludeUnprefixedFallback = true,     // serve plain keys too (the default)
    FallBackToLocalConfig     = true      // false: require the Lockjaw app
});

Values the Lockjaw app publishes carry their environment name with them, so none of this affects those — it shapes the fallback only.

API

Member Purpose
Get(key) / Get<T>(key, fallback) / GetRequired(key) Resolve an app setting (including top-level pairs)
GetConnectionString(name) Resolve a connection string
this[key] / GetSection / GetChildren / GetReloadToken IConfiguration — read by Section:Key path
ToConfigurationSource() / AddLockjaw(builder) Add Lockjaw to an IConfigurationBuilder pipeline
EnvironmentOf(key, kind) The environment name a key resolves to
InstanceName The instance this client resolved to
SourceOf(key, kind) Published, LocalConfig, or None
All Every setting the client can resolve
Environments The environments the app published, with names and colors
IsAvailable / HasLocalFallback / UpdatedUtc Whether the app has published, whether a fallback loaded, and when
SettingChanged Raised when a value changes (background thread)
SubscribeOnUiThread(handler) / SubscribeOn(context, handler) Subscribe with events marshalled to the UI thread
Refresh() Re-read now

Options

Option Default
InstanceName default Must match the app's instance
AutoDiscoverInstance false Find the instance that names this application
ApplicationName the entry assembly's name What discovery matches on
SnapshotPath derived from the instance Explicit file to read
WatchForChanges true Receive live updates (pushed over a SIPC semaphore mesh)
AllowCrossAccountHost false Join the app's mesh across a session/account boundary
FallBackToLocalConfig true Read own config when the app has published nothing
FallbackEnvironment Development Environment the fallback resolves to
Environments Development_ / Production_ Prefixes recognised in the fallback
IncludeUnprefixedFallback true Serve plain keys from own config
UseLocalAppConfig / UseLocalJsonFile true Which fallback sources to read
LocalJsonFilePath appsettings.json beside the app Fallback JSON file

LockjawClient is thread-safe and implements IDisposable — dispose it to stop listening. Use LockjawClient.Default for the common single-instance case.

Clients under a different account

The mesh's semaphores are session-local by default, so a same-user client on the same desktop hears the push with no configuration. Reaching a client in another logon session or under a different Windows account — a service account, say — puts the semaphores in the global namespace, which both sides must opt into: set AllowCrossAccountClients on the app/host side (LockjawOptions, in Lockjaw.Core) and AllowCrossAccountHost on the client side. The app's option is off by default because the global namespace lets any authenticated account on the machine read the published values, and it may require privilege to create.

Shipping

Lockjaw is a development aid. Keep production credentials out of config files that reach end users; the usual approach is to reference it from Debug builds only:

<ItemGroup Condition="'$(Configuration)' == 'Debug'">
  <PackageReference Include="Lockjaw.Client" Version="2.0.1" />
</ItemGroup>

See also

  • Lockjaw.Core — the shared model, if you want to read the published snapshot yourself.
  • The Lockjaw app — the tray application that reads your configuration and publishes the values. Its built-in Help (F1) documents the whole system, including the real file paths on your machine.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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
2.0.2.51 83 8/14/2026
2.0.2.49 81 8/14/2026
2.0.2.37 90 8/6/2026
2.0.2.34 100 8/5/2026
2.0.2.33 86 8/5/2026
2.0.2.32 74 8/5/2026
2.0.2.28 89 8/5/2026
2.0.2.27 89 8/5/2026
2.0.2.17 106 7/25/2026

2.2.0
- Live updates now travel over SIPC, a semaphore-only peer mesh, instead of a named pipe — still a direct push from the Lockjaw app, with no sockets, pipes or shared files. Changes are delivered promptly even on locked-down machines where file-change notifications are unreliable.
- The client joins the app's mesh, subscribes for changes, and asks the app for the current snapshot on join, retrying until the app answers or appears. It still reads the last published snapshot at startup, so values stay available when the app is not running.
- Set AllowCrossAccountHost to receive updates from an app running in another logon session or under a different Windows account; it must match AllowCrossAccountClients on the app/host side. A same-user app on the same desktop needs no configuration.
- SettingChanged now fires when a previously published key is withdrawn — the app stops publishing it, or is disabled and publishes an empty snapshot — reporting the value it reverts to (the client's own config, or none). Previously a withdrawn value could leave consumers, and the IConfiguration provider, on the stale published value.
- The local fallback now resolves prefixed keys to the environment the client's own config declares as default (Lockjaw:DefaultEnvironment), so a key reverts to its default-environment value when nothing is published, exactly as on a production machine. FallbackEnvironment is now unset by default and, when set, still pins the fallback to a specific environment; it previously defaulted to Development.

2.1.0
- Live updates now arrive as a direct push from the Lockjaw app over a named pipe, replacing the snapshot-file watch. Changes are delivered promptly even on locked-down machines where file-change notifications are unreliable — redirected profiles, VDI/roaming setups, or aggressive endpoint protection.
- The client no longer uses FileSystemWatcher: it listens on the pipe and reconnects on its own if the app is not running yet or restarts. It still reads the last published snapshot at startup, so values stay available when the app is not running.
- Clients in another logon session are reached with no configuration; a client under a different Windows account is supported by enabling AllowCrossAccountClients on the app/host side.

2.0.2
- Environments are open-ended: declare any prefix (Local_, Test_, Staging_) instead of only Development_/Production_.
- EnvironmentOf returns the environment name; Environments exposes the published environment list.
- Falls back to the consuming application's own app.config / appsettings.json when nothing has been published; SourceOf reports where a value came from.
- SubscribeOnUiThread / SubscribeOn marshal SettingChanged onto a WPF or WinForms UI thread.
- Recovers from FileSystemWatcher buffer overflow so a dropped event cannot leave stale values.