Stashbox Documentation
website·Indexed 29 days ago
https://z4kn4fein.github.io/stashboxA .NET dependency injection container featuring service registration, lifetime management, and support for decorators, generics, and child containers. Includes guides on factory registration, unit-of-work scopes, and registration validation using InvalidRegistrationException.
What's inside Stashbox
- Stashbox is a lightweight, fast, and portable dependency injection framework for .NET-based solutions designed to facilitate loosely coupled applications and hierarchical object structures. It features a Fluent configuration API, thread-safe and lock-free operations, a small memory footprint, and built-in detection for dependency cycles and misconfigurations.
Overview of pre-defined resolvers and wrappers
Stashbox uses
IServiceWrapperandIServiceResolverimplementations to handle special resolution cases that standard service registrations cannot fulfill.Key Wrappers:
EnumerableWrapper: HandlesIEnumerable<>,IList<>, etc.LazyWrapper: HandlesLazy<>.FuncWrapper: HandlesFunc<>and other non-void delegates.MetadataWrapper: HandlesValueTuple<,>,Tuple<,>, orMetadata<,>.KeyValueWrapper: HandlesKeyValuePair<,>orReadOnlyKeyValue<,>.
Key Resolvers:
ServiceProviderResolver: Resolves the scope asIServiceProvider.OptionalValueResolver: Handles optional parameters.DefaultValueResolver: Handles default values.ParentContainerResolver: Resolves services from parent containers.UnknownTypeResolver: Handles services not registered in the container.
Understand Service and Implementation types in Stashbox
In Stashbox, a Service type is the interface or abstract class used for resolution or dependency injection. The Implementation type is the concrete class registered to that service type. The implementation type must extend or implement the service type.// Map Service type (IService) to Implementation type (Implementation) container.Register<IService, Implementation>(); // Resolve using the Service type var service = container.Resolve<IService>(); // returns an instance of ImplementationAccess the resolving type context in factories
To access metadata about the type currently being resolved (useful for open generics), includeTypeInformationas a parameter in the factory delegate. TheTypeInformationobject contains the reflected context of the requested type.container.Register(typeof(IService<>), typeof(Service<>), options => options.WithFactory<TypeInformation>(typeInfo => { // typeInfo.Type holds the actual closed generic type (e.g., IService<int>) return new Service<object>(); // Example return })); container.Resolve<IService<int>>();Understand Injectable Dependencies and Resolution Trees
An Injectable dependency is a constructor argument, method argument, or property/field of a registered implementation type that Stashbox evaluates and injects during construction.
The Resolution tree is the structural representation of this process, defining the instantiation order. Services without dependencies are instantiated first, then injected into the services that depend on them.
// Implementation has an injectable dependency (IDependency) in its constructor class Implementation : IService { public Implementation(IDependency dependency) { } }Access the resolving type information in a factory
To access metadata about the type currently being resolved (useful for open generics), includeTypeInformationas a parameter in the.WithFactorydelegate. TheTypeInformationobject contains reflected context about the requested type.interface IService<T> { } class Service<T> : IService<T> { } container.Register(typeof(IService<>), typeof(Service<>), options => options.WithFactory<TypeInformation>(typeInfo => { // typeInfo.Type will be IService<int> if Resolve<IService<int>>() is called return new Service<object>(); // Example return })); container.Resolve<IService<int>>();Implement a custom service lifetime
Custom lifetimes can be created by inheriting from eitherExpressionLifetimeDescriptor(for expression-based instantiation) orFactoryLifetimeDescriptor(for pre-compiled factory delegates). You must override theApplyLifetimemethod to define the instance management logic.// Example using ExpressionLifetimeDescriptor class CustomLifetime : ExpressionLifetimeDescriptor { protected override Expression ApplyLifetime( Expression expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type requestedType) { // Lifetime managing functionality return expression; } } // Registration container.Register<IJob, DbBackup>(options => options.WithLifetime(new CustomLifetime()));Implement open-generic decorators
Stashbox supports open-generic decorators to extend open-generic services. This includes support for generic parameter constraints and variance handling.interface IEventProcessor<TEvent> { void ProcessEvent(TEvent @event); } class GeneralEventProcessor<TEvent> : IEventProcessor<TEvent> { /* ... */ } class ValidatorProcessor<TEvent> : IEventProcessor<TEvent> { private readonly IEventProcessor<TEvent> nextProcessor; public ValidatorProcessor(IEventProcessor<TEvent> eventProcessor) { this.nextProcessor = eventProcessor; } public void ProcessEvent(TEvent @event) { /* ... */ } } using var container = new StashboxContainer(); container.Register(typeof(IEventProcessor<>), typeof(GeneralEventProcessor<>)); container.RegisterDecorator(typeof(IEventProcessor<>), typeof(ValidatorProcessor<>)); var eventProcessor = container.Resolve<IEventProcessor<UpdateEvent>>();Register multiple types using batch registration
UseRegisterTypesto register a collection of types to all their implemented interfaces, extended base classes, and themselves (self-registration). By default, framework types likeIDisposableare excluded as service types. You can optionally provide an implementation filter and a registration configurator action to customize individual registrations.container.RegisterTypes(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }); IEnumerable<IJob> jobs = container.ResolveAll<IJob>(); // Returns implementations of IJob ILogger logger = container.Resolve<ILogger>(); // Returns ConsoleLogger DbBackup backup = container.Resolve<DbBackup>(); // Returns DbBackup (self-registration)Verify container state using Validate method
Stashbox provides a.Validate()method that allows you to verify the container's actual state. This method walks through the entire resolution tree and collects all detected misconfiguration issues into anAggregateException.Configure and resolve services with Stashbox
This example demonstrates how to decouple services using interfaces and configure them within aStashboxContainerin a Console Application. It shows registering a singleton logger, a transient event broadcaster, and a job service, then resolving the job to execute its logic.using Stashbox; using System; public interface IJob { void DoTheJob(); } public interface ILogger { void Log(string message); } public interface IEventBroadcaster { void Broadcast(IEvent @event); } public class ConsoleLogger : ILogger { public void Log(string message) => Console.WriteLine(message); } public class MessageBus : IEventBroadcaster { private readonly ILogger logger; public MessageBus(ILogger logger) { this.logger = logger; } void Broadcast(IEvent @event) => this.logger.Log($"Sending event to bus: {@event.Name}"); } public class DbBackup : IJob { private readonly ILogger logger; private readonly IEventBroadcaster eventBroadcaster; public DbBackup(ILogger logger, IEventBroadcaster eventBroadcaster) { this.logger = logger; this.eventBroadcaster = eventBroadcaster; } public void DoTheJob() { this.logger.Log("Backing up!"); this.eventBroadcaster.Broadcast(new DbBackupCompleted()); } } public class Program { private static readonly IStashboxContainer container; static Program() { // 1. Create container container = new StashboxContainer(); // 2. Register your services container.RegisterSingleton<ILogger, ConsoleLogger>(); container.Register<IEventBroadcaster, MessageBus>(); container.Register<IJob, DbBackup>(); // 3. Validate the configuration. container.Validate(); } static void Main(string[] args) { // 4. Resolve and use your service var job = container.Resolve<IJob>(); job.DoTheJob(); } }Register multiple types using batch registration
UseRegisterTypesto register a collection of types to all their implemented interfaces, extended base classes, and themselves (self-registration). By default, framework types likeIDisposableare excluded from being considered as service types.container.RegisterTypes(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }); IEnumerable<IJob> jobs = container.ResolveAll<IJob>(); // Returns implementations of IJob ILogger logger = container.Resolve<ILogger>(); // Returns ConsoleLogger DbBackup backup = container.Resolve<DbBackup>(); // Returns DbBackup (self-registration)