Advanced Registration
Self Registration
Section titled “Self Registration”Register a class as itself rather than against its interface:
[Singleton(AsSelf = true)]public class AppState : IStandardInterface;
// Registers as: services.AddSingleton<AppState>();// NOT as: services.AddSingleton<IStandardInterface, AppState>();Multiple Interfaces
Section titled “Multiple Interfaces”When a class implements multiple interfaces and is registered as Singleton or Scoped, Shiny automatically registers it against all interfaces with shared instance resolution:
[Singleton]public class MultiService : IServiceA, IServiceB { }// Both IServiceA and IServiceB resolve to the same MultiService instanceTo target a specific interface instead:
[Singleton(Type = typeof(IServiceA))]public class MultiService : IServiceA, IServiceB { }// Only registered against IServiceAKeyed Services
Section titled “Keyed Services”Register services with a key for disambiguation:
[Singleton(KeyedName = "primary")]public class PrimaryCache : ICache { }
[Singleton(KeyedName = "fallback")]public class FallbackCache : ICache { }TryAdd Semantics
Section titled “TryAdd Semantics”Prevent overwriting an existing registration:
[Singleton(TryAdd = true)]public class DefaultLogger : ILogger { }// Uses TryAddSingleton — won't replace an ILogger that's already registeredFactory-Form Source Generation
Section titled “Factory-Form Source Generation”Classes attributed with [Singleton]/[Scoped]/[Transient]/[Service] are emitted in factory form — the generator expands the constructor and wires DI lookups at compile time. No reflection at runtime.
[Singleton]public class MyService(IDep dep) : IMyService { }
// Generated:// services.AddSingleton<IMyService>(sp => new MyService(sp.GetRequiredService<IDep>()));Constructor selection matches ActivatorUtilities rules:
[ActivatorUtilitiesConstructor]is honored- Otherwise, the constructor with the most parameters is picked
[FromKeyedServices("key")]parameters resolve viaGetRequiredKeyedService<T>("key")IServiceProviderparameters are passed through directly
Multi-interface classes emit explicit forwarders rather than relying on reflection:
[Singleton]public class MyService : IFoo, IBar { }
// Generated:// services.AddSingleton<MyService>(sp => new MyService());// services.AddSingleton<IFoo>(sp => sp.GetRequiredService<MyService>());// services.AddSingleton<IBar>(sp => sp.GetRequiredService<MyService>());Because every attributed registration is factory-based, resolve chains like OnResolved and BindOnResolve compose naturally.
Resolve Chains
Section titled “Resolve Chains”Chain a callback onto the most recently registered factory-based service. The callback fires after the container constructs the instance but before it’s returned. Fires once per construction: singleton → once total, scoped → once per scope, transient → every resolve.
services .AddSingleton<IFoo>(sp => new Foo()) .OnResolved<IFoo>((foo, sp) => foo.Configure(sp.GetRequiredService<IOptions>()));
// Action<TService> overload when the IServiceProvider isn't neededservices .AddSingleton<IFoo>(sp => new Foo()) .OnResolved<IFoo>(foo => foo.Initialize());The chain is AOT-clean — it wraps the factory directly. Type-based registrations (AddSingleton<TService, TImpl>()) and pre-built instance registrations are rejected at registration time; use a factory-based form instead (or rely on the source generator, which always emits factory form).