Shiny Extensions

May 27, 2026 · View on GitHub

Dependency Injection Extensions

  • 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 OnResolved compose 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

The Results

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;
        }
    }
}

Setup

  1. Install the NuGet package Shiny.Extensions.DependencyInjection
  2. Add the following using directive:
    // during your app startup - use your service collection 
    builder.Services.AddGeneratedServices();
    
  3. Add the [Service(ServiceLifetime.Singleton, "optional key")] attribute to your classes and specify the lifetime and optional key

Stores

  • 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 IKeyValueStore to plug in your own store

Setup

  1. Install the NuGet package Shiny.Extensions.Stores
  2. Register at startup — the static Shiny.Stores accessor self-bootstraps on first use:
    builder.Services.AddShinyStores();
    var host = builder.Build();
    
    On Blazor WebAssembly (where LocalStorageKeyValueStore needs IJSRuntime), also call host.Services.UseShinyStores() after Build() to snapshot the DI-resolved store into the static accessor.
  3. Define your settings as a partial class 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; }
    }
    
  4. Inject AppSettings anywhere. 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");

Available Stores Per Platform

PlatformKeyDescription
AndroidStoreKeys.DefaultPreferences
AndroidStoreKeys.SecureSecure Storage
iOSStoreKeys.DefaultNSUserDefaults
iOSStoreKeys.SecureKeychain
WindowsStoreKeys.DefaultApplicationData.LocalSettings
WindowsStoreKeys.SecureSecure Storage
WebAssemblyStoreKeys.DefaultlocalStorage
WebAssembly"session"sessionStorage
AllanyIn-memory dictionary (great for testing)

Note

For WebAssembly, install the Shiny.Extensions.Stores.Web package and add services.AddShinyWebAssemblyStores() to your service collection.

Web Hosting Extensions

  • Merges service container build and post build scenarios into a single class using IWebModule

Setup

  1. Install the NuGet package Shiny.Extensions.WebHosting
  2. 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
        }
    }
    
  3. 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();
    

MAUI Hosting Extensions

  • Module-based MAUI app configuration with IMauiModule
  • Static Host.Services for 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

Setup

  1. Install the NuGet package Shiny.Extensions.MauiHosting
  2. 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)
        }
    }
    
  3. 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();
    
  4. Access services anywhere via Host.Services

IAppSupport

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();
    }
}

IAppStore

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();
}

Additional Libraries Used

NuGet Packages

PackageDescription
Shiny.Extensions.DependencyInjectionAttribute-driven DI registration with source generators
Shiny.Extensions.StoresCross-platform key/value store abstraction
Shiny.Extensions.Stores.WebBlazor WebAssembly localStorage/sessionStorage
Shiny.Extensions.WebHostingASP.NET modular web hosting with IWebModule
Shiny.Extensions.MauiHostingMAUI modular hosting with IMauiModule and platform lifecycle hooks