Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration!How!?

Stores Release Notes

Fix
Desktop and Linux builds resolving the base net10.0 asset previously used an in-memory store for StoreKeys.Default/StoreKeys.Secure, silently losing all settings on restart. They now use the new persistent FileKeyValueStore, which writes to {LocalApplicationData}/{EntryAssemblyName} (override with Shiny.Stores.FileStoreDirectory)
Feature
New net10.0-macos target — plain macOS desktop apps now get NSUserDefaults + Keychain (real secure storage) via the same Apple code as iOS/Mac Catalyst, instead of the plaintext file fallback
Feature
New FileKeyValueStore — a persistent, JSON-file-backed IKeyValueStore (atomic writes, corrupt-file recovery) used automatically on base-net10.0 desktop targets
EnhancementWindows
Unpackaged desktop apps (WPF/WinForms/console targeting net10.0-windows) no longer throw on ApplicationData.Current. When no package identity is detected, Default falls back to FileKeyValueStore and Secure layers DPAPI over it; packaged (MSIX) apps keep the native ApplicationData stores
BREAKINGFix
The Apple SecureKeyValueStore default DefaultAccessible changed from the now fully-deprecated SecAccessible.Always to SecAccessible.AfterFirstUnlock (keychain items are accessible after the first device unlock following boot, including while subsequently locked). Set DefaultAccessible explicitly if you need different behavior. Resolves the CA1422 obsolete-API warning
Fix
StoreExtensions.Get<T>(store, key, defaultValue) now checks Contains(key) before reading, so a stored 0, false, or default-enum value is no longer mistaken for a missing key and replaced with the supplied default
Enhancement
Shiny.Stores.Default and Shiny.Stores.Secure are now self-bootstrapping. On mobile/desktop they lazily construct the platform-native store on first access — no post-build UseShinyStores() call is required. Generated [Bind] property accessors will work even if read during host construction (fixes InvalidOperationException: Shiny stores not initialized for code paths that touch settings before the post-build init ran)
Feature
New Shiny.Stores.Register(object key, IKeyValueStore store) — registers or overrides a store for any key. Use for tests, custom store keys referenced by [Bind("custom-key")], or scenarios where the store can’t be constructed statically (Blazor IJSRuntime-backed stores)
Feature
New Shiny.Stores.Serializer — single shared DefaultSerializer instance used by both the static accessor and DI. services.AddJsonContext(...) now installs its context onto this same instance, so static and DI consumers can’t drift
Feature
New Shiny.Stores.Reset() — clears all static registrations and forces re-bootstrap on next access (useful for test isolation)
Enhancement
UseShinyStores() is now optional on mobile/desktop. It remains useful for Blazor (where LocalStorageKeyValueStore needs IJSRuntime from the built provider) and for snapshotting keyed IKeyValueStore registrations from DI into the static accessor
BREAKINGEnhancement
IKeyValueStore has been reshaped to a generic, AOT-friendly API. The Alias property is gone; object? Get(Type, string) and Set(string, object) have been replaced by T? Get<T>(string) and Set<T>(string, T)
BREAKINGEnhancement
ISerializer is now generic-first (T Deserialize<T> / string Serialize<T>). Runtime-typed overloads object? Deserialize(Type, string) and string Serialize(object, Type?) remain for the object binder
BREAKINGEnhancement
DefaultSerializer is now AOT-clean and strict — types must be supplied via a JsonSerializerContext. Register one with services.AddJsonContext(MyJsonContext.Default). There is no longer a reflection-based fallback by default; for non-AOT scenarios (e.g. unit tests) add serializer.Options.TypeInfoResolverChain.Add(new DefaultJsonTypeInfoResolver()) explicitly
Feature
New StoreKeys constants (StoreKeys.Default = "settings", StoreKeys.Secure = "secure") — stores are now registered as keyed singletons in DI instead of by Alias. Resolve with sp.GetRequiredKeyedService<IKeyValueStore>(StoreKeys.Default)
BREAKINGEnhancement
IKeyValueStoreFactory and KeyValueStoreFactory have been removed. Use keyed DI lookups against StoreKeys (or your own keys) instead of factory.GetStore(alias)
BREAKINGEnhancement
IObjectStoreBinder.Bind now takes object? storeKey (a DI service key) instead of string? keyValueStoreAlias. The implementation now depends on IServiceProvider + ISerializer, not IKeyValueStoreFactory
BREAKINGEnhancement
ObjectStoreBinderAttribute.StoreAlias has been renamed to StoreKey
BREAKINGEnhancement
AddPersistentService<TImpl>(string? alias) is now AddPersistentService<TImpl>(Func<IServiceProvider, TImpl> factory, object? storeKey = null). A factory is required to keep registration AOT-clean. The non-generic AddPersistentService(Type, ...) overload has been removed
Feature
New static Shiny.Stores accessor — Stores.Default, Stores.Secure, and Stores.Keyed(key) resolve the registered IKeyValueStore for the corresponding service key. Initialize once after the service provider is built by calling serviceProvider.UseShinyStores() (or the lower-level Stores.Initialize(serviceProvider))
Feature
Object binding is now source-generated via the new [Bind] attribute (in Shiny.Extensions.DependencyInjection). The DI source generator emits partial property bodies that call Stores.Default.Set/Get (or Stores.Secure, Stores.Keyed(...)). No INotifyPropertyChanged required, no runtime reflection, fully AOT-clean
BREAKINGEnhancement
Removed IObjectStoreBinder / ObjectStoreBinder, ObjectStoreBinderAttribute, BindOnResolve(), and AddPersistentService<T>(). The runtime INPC-based binder has been replaced by source-generated [Bind] properties — migrate by marking your settings class partial, declaring [Bind] partial properties, and (optionally) marking the class [Singleton] for auto-registration
Feature
New services.AddJsonContext(JsonSerializerContext) extension — installs (or chains to) the shared DefaultSerializer and registers types for AOT-safe serialization
Feature
Repositories have been folded into Shiny.Extensions.Stores. New IRepository + IRepositoryEntity API with the FileSystemRepository implementation (transactional, in-memory-cached, {EntityName}_{Id}.shiny files), RepositoryAction change-event enum, RepositoryException, and services.AddDefaultRepository(DirectoryInfo? rootDir = null) registration helper
FeatureBlazor WebAssembly
New LocalStorageRepository — mirrors FileSystemRepository over the browser’s localStorage via shiny:repo:{EntityName}: prefixed keys. Register with services.AddLocalStorageRepository()
BREAKINGEnhancementBlazor WebAssembly
LocalStorageKeyValueStore has been rewritten to use ISerializer and prefix-namespaced keys (shiny:kvs:settings:). Register with services.AddLocalStorageKeyValueStore() — registers under StoreKeys.Default and exposes the unkeyed default
BREAKINGEnhancementBlazor WebAssembly
BaseKeyValueStore and SessionStorageKeyValueStore have been removed. The Web package now requires the _content/Shiny.Extensions.Stores.Web/shiny-storage.js script to be loaded in index.html before blazor.webassembly.js
BREAKINGEnhancement
FileKeyValueStore has been removed — use the new IRepository API for entity-style persistence
FixAndroid
SecureKeyValueStore is now functional — its constructor was previously commented out and the class could not be instantiated
FixWindows
SecureKeyValueStore had a syntactically broken constructor; the class is now functional
Enhancement
Updated to align with Shiny Extensions 2.0 — no API changes
Fix
Booleans with false were not binding properly on stores
FixiOS
Secure storage would not correctly store or clear all values
Feature
Initial Public Release