Help us improve
Share bugs, ideas, or general feedback.
From shiny-extensions
Generate and configure Shiny DI for .NET - attribute-driven service registration with source generators, keyed services, categories, and multi-interface support.
npx claudepluginhub shinyorg/extensions --plugin shiny-extensionsHow this skill is triggered — by the user, by Claude, or both
Slash command
/shiny-extensions:shiny-diThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert in Shiny Extensions Dependency Injection, a .NET library providing attribute-driven service registration with source generators.
Creates p5.js generative art with seeded randomness, noise fields, and interactive parameter exploration. Use for algorithmic art, flow fields, or particle systems.
Share bugs, ideas, or general feedback.
You are an expert in Shiny Extensions Dependency Injection, a .NET library providing attribute-driven service registration with source generators.
Invoke this skill when the user wants to:
[Singleton], [Scoped], or [Transient] attributes with source generationDocumentation: https://shinylib.net/extensions/di/
Repository: https://github.com/shinyorg/Shiny.Extensions
Package: Shiny.Extensions.DependencyInjection
Namespace: Shiny
Mark classes with attributes for automatic DI registration via source generation:
[Singleton] // Singleton lifetime
public class MyService : IMyService { }
[Scoped] // Scoped lifetime
public class MyRepository : IRepository { }
[Transient] // Transient lifetime
public class MyFactory : IFactory { }
All registration attributes support these properties:
| Property | Type | Description |
|---|---|---|
AsSelf | bool | Register as the class itself rather than its interface |
Type | Type | Register against a specific interface (when class implements multiple) |
KeyedName | string? | Register as a keyed service |
Category | string? | Category for conditional registration |
TryAdd | bool | Use TryAdd semantics (won't replace existing registrations) |
// Register as the class itself
[Singleton(AsSelf = true)]
public class AppState { }
// Register against a specific interface
[Singleton(Type = typeof(ISpecificInterface))]
public class MultiInterfaceService : ISpecificInterface, IOtherInterface { }
// Keyed service
[Singleton(KeyedName = "primary")]
public class PrimaryCache : ICache { }
// Category-based conditional registration
[Singleton(Category = "premium")]
public class PremiumFeature : IFeature { }
// TryAdd semantics
[Singleton(TryAdd = true)]
public class DefaultLogger : ILogger { }
// Register all source-generated services
builder.Services.AddShinyServiceRegistry();
// Register with specific categories only
builder.Services.AddShinyServiceRegistry("premium", "analytics");
// Register a singleton against all implemented interfaces
services.AddSingletonAsImplementedInterfaces<MyService>();
services.AddSingletonAsImplementedInterfaces<MyService>("keyName");
// Register scoped against all implemented interfaces
services.AddScopedAsImplementedInterfaces<MyService>();
// Check registrations
bool hasService = services.HasService<IMyService>();
bool hasImpl = services.HasImplementation<MyService>();
// Lazy resolution
Lazy<IMyService> lazy = services.GetLazyService<IMyService>(required: true);
Every [Service]/[Singleton]/[Scoped]/[Transient] attributed class is emitted in factory form — the source generator expands the constructor at compile time so registrations are AOT-clean and chain-friendly. Constructor selection mirrors ActivatorUtilities: [ActivatorUtilitiesConstructor] wins, otherwise the longest constructor is chosen. [FromKeyedServices("k")] and IServiceProvider parameters are handled. Multi-interface classes get explicit forwarders (no AddSingletonAsImplementedInterfaces reflection).
[Singleton]
public class MyService(IDep dep) : IMyService { }
// Generated:
// services.AddSingleton<IMyService>(sp => new MyService(sp.GetRequiredService<IDep>()));
Chain a callback onto the most recently registered factory-based service. Fires once per factory invocation (singleton → once; scoped → per-scope; transient → every resolve). Fully AOT-clean — wraps the factory, no reflection. Type-based and pre-built instance registrations are rejected (but generator-emitted registrations are always factory form, so this just works).
// With IServiceProvider
services
.AddSingleton<IFoo>(sp => new Foo())
.OnResolved<IFoo>((foo, sp) => foo.Configure(sp.GetRequiredService<IOptions>()));
// Without IServiceProvider (Action<TService> overload)
services
.AddSingleton<IFoo>(sp => new Foo())
.OnResolved<IFoo>(foo => foo.Initialize());
Mark a partial property [Bind] on a partial class. The generator emits the property body that reads/writes a Shiny key/value store via the static Shiny.Stores accessor (requires Shiny.Extensions.Stores). No INotifyPropertyChanged, no reflection, AOT-clean.
[Singleton]
public partial class AppSettings
{
[Bind] // default store
public partial string Theme { get; set; }
[Bind("secure")] // secure store
public partial string Token { get; set; }
[Bind(Key = "ui_density")] // override storage key (defaults to property name)
public partial int Density { get; set; }
}
[Singleton], [Scoped], [Transient]) over manual registrationCategory for optional features that may not always be registeredKeyedName when multiple implementations of the same interface existAddShinyServiceRegistry() to activate source-generated registrations[Singleton]/[Scoped]/[Transient] attributes over manual services.Add*() callsKeyedName to disambiguate