- Source generate all attributed classes to a single add file - saves you the boilerplate
- Factory-form emission - generated registrations expand the constructor at compile time (no reflection, AOT-clean) so resolve chains like
OnResolvedcompose naturally ActivatorUtilities-style constructor selection ([ActivatorUtilitiesConstructor]and[FromKeyedServices]honored)- Multiple interfaces via explicit forwarders (no reflection)
- Supports open generics and keyed services
OnResolved<T>(hook)chain extension for one-shot post-construction hooks
THIS:
using Microsoft.Extensions.DependencyInjection;
using Shiny;
// given the following code from a user
namespace Sample
{
public interface IStandardInterface;
public interface IStandardInterface2;
[Service(ServiceLifetime.Singleton)]
public class ImplementationOnly;
[Service(ServiceLifetime.Transient, "ImplOnly")]
public class KeyedImplementationOnly;
[Service(ServiceLifetime.Singleton)]
public class StandardImplementation : IStandardInterface;
[Service(ServiceLifetime.Scoped, "Standard")]
public class KeyedStandardImplementation : IStandardInterface;
[Service(ServiceLifetime.Singleton)]
public class MultipleImplementation : IStandardInterface, IStandardInterface2;
[Service(ServiceLifetime.Scoped)]
public class ScopedMultipleImplementation : IStandardInterface, IStandardInterface2;
[Service(ServiceLifetime.Scoped, "KeyedGeneric")]
public class TestGeneric<T1, T2>
{
public T1 Value1 { get; set; }
public T2 Value2 { get; set; }
}
}GENERATES THIS:
// <auto-generated />
using global::Microsoft.Extensions.DependencyInjection;
using global::Shiny;
namespace Sample
{
public static class __GeneratedRegistrations
{
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddGeneratedServices(
this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services
)
{
services.AddSingleton<global::Sample.ImplementationOnly>();
services.AddKeyedTransient<global::Sample.KeyedImplementationOnly>("ImplOnly");
services.AddSingleton<global::Sample.IStandardInterface, global::Sample.StandardImplementation>();
services.AddKeyedScoped<global::Sample.IStandardInterface, global::Sample.KeyedStandardImplementation>("Standard");
services.AddSingletonAsImplementedInterfaces<global::Sample.MultipleImplementation>();
services.AddScopedAsImplementedInterfaces<global::Sample.ScopedMultipleImplementation>();
services.AddKeyedScoped(typeof(global::Sample.TestGeneric<,>), "KeyedGeneric");
return services;
}
}
}- Install the NuGet package
Shiny.Extensions.DependencyInjection - Add the following using directive:
// during your app startup - use your service collection builder.Services.AddGeneratedServices();
- Add the
[Service(ServiceLifetime.Singleton, "optional key")]attribute to your classes and specify the lifetime and optional key
- Cross-platform key/value store with support for
- Android/iOS/Windows - Preferences & Secure Storage
- Web - Local Storage
- In Memory
- Source-generated
[Bind]attribute on partial properties - emits getter/setter bodies that round-trip through the store (no INPC required, no runtime reflection, fully AOT) - Static
Shiny.Stores.Default/Secure/Keyed(...)accessor for direct ad-hoc reads/writes - Implement
IKeyValueStoreto plug in your own store
- Install the NuGet package
Shiny.Extensions.Stores - Register at startup — the static
Shiny.Storesaccessor self-bootstraps on first use:On Blazor WebAssembly (wherebuilder.Services.AddShinyStores(); var host = builder.Build();
LocalStorageKeyValueStoreneedsIJSRuntime), also callhost.Services.UseShinyStores()afterBuild()to snapshot the DI-resolved store into the static accessor. - Define your settings as a
partialclass with[Bind]partial properties:using Shiny; [Singleton] public partial class AppSettings { [Bind] // default store public partial string Theme { get; set; } [Bind("secure")] // secure store public partial string Token { get; set; } }
- Inject
AppSettingsanywhere. Set properties — they persist. Read properties — they come from the store.
Or skip the class and use the static accessor:
Shiny.Stores.Default.Set("theme", "dark");
var theme = Shiny.Stores.Default.Get<string>("theme");| Platform | Key | Description |
|---|---|---|
| Android | StoreKeys.Default |
Preferences |
| Android | StoreKeys.Secure |
Secure Storage |
| iOS | StoreKeys.Default |
NSUserDefaults |
| iOS | StoreKeys.Secure |
Keychain |
| Windows | StoreKeys.Default |
ApplicationData.LocalSettings |
| Windows | StoreKeys.Secure |
Secure Storage |
| WebAssembly | StoreKeys.Default |
localStorage |
| WebAssembly | "session" |
sessionStorage |
| All | any | In-memory dictionary (great for testing) |
Note
For WebAssembly, install the Shiny.Extensions.Stores.Web package and add services.AddShinyWebAssemblyStores() to your service collection.
- Merges service container build and post build scenarios into a single class using
IWebModule
- Install the NuGet package
Shiny.Extensions.WebHosting - Add a web module by implementing
IWebModule:using Shiny; public class MyWebModule : IWebModule { public void Add(WebApplicationBuilder builder) { // Register your services here } public void Use(WebApplication app) { // Configure your middleware/endpoints here } }
- In your application hosting startup, add the following:
using Shiny; var builder = WebApplication.CreateBuilder(args); builder.AddInfrastructureModules(new MyWebModule()); var app = builder.Build(); app.UseInfrastructureModules();
- Module-based MAUI app configuration with
IMauiModule - Static
Host.Servicesfor accessing the service provider anywhere IAppSupport— device info, browser/map launch, programmatic orientation lock, and live change events for orientation, culture, and time zone (native listeners on iOS/Android/Windows, polling fallback elsewhere)IAppStore— cross-platform store version lookups + deep links for Apple App Store (iTunes Search API), Google Play (HTML scrape), and Microsoft Store (DisplayCatalog API)- Opt-in registration: each capability is its own extension method so apps only pay for what they use
- Install the NuGet package
Shiny.Extensions.MauiHosting - Create a MAUI module by implementing
IMauiModule:using Shiny; public class MyMauiModule : IMauiModule { public void Add(MauiAppBuilder builder) { // Register your services here } public void Use(IPlatformApplication app) { // Post-build initialization here (do NOT block) } }
- In your
MauiProgram.cs:using Shiny; var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .AddInfrastructureModules(new MyMauiModule()) // your IMauiModule list .AddAppSupport() // IAppSupport .AddAppStore(opts => // optional: IAppStore + config { opts.AppleAppId = "1234567890"; opts.WindowsProductId = "9NBLGGH4NNS1"; }); return builder.Build();
- Access services anywhere via
Host.Services
public class MyVm(IAppSupport app)
{
void Hook()
{
// Snapshot
var version = app.AppVersion;
var orientation = app.CurrentOrientation;
var culture = app.CurrentCulture;
// Live updates
app.OrientationChanged += (s, e) => { /* new DisplayOrientation */ };
app.CultureChanged += (s, e) => { /* new CultureInfo */ };
app.TimeZoneChanged += (s, e) => { /* new TimeZoneInfo */ };
// Programmatic orientation lock
_ = app.SetOrientation(DisplayOrientation.Landscape);
_ = app.ResetOrientation();
}
}public class UpdateChecker(IAppStore store)
{
public async Task Check()
{
var result = await store.GetCurrent();
if (result?.NeedsUpdate == true)
await store.OpenStore();
}
public Task Review() => store.OpenReviewPage();
}- Shiny Reflector - Reflection without the actual reflection
| Package | Description |
|---|---|
Shiny.Extensions.DependencyInjection |
Attribute-driven DI registration with source generators |
Shiny.Extensions.Stores |
Cross-platform key/value store abstraction |
Shiny.Extensions.Stores.Web |
Blazor WebAssembly localStorage/sessionStorage |
Shiny.Extensions.WebHosting |
ASP.NET modular web hosting with IWebModule |
Shiny.Extensions.MauiHosting |
MAUI modular hosting with IMauiModule and platform lifecycle hooks |