Stashbox Documentation

website·Indexed 29 days ago

https://z4kn4fein.github.io/stashbox

A .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.

Tokens
22.8K
Snippets
143
Records
154
Agent score
74%

What's inside Stashbox

  1. Overview of Stashbox Dependency Injection

    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.
  2. Overview of pre-defined resolvers and wrappers

    Stashbox uses IServiceWrapper and IServiceResolver implementations to handle special resolution cases that standard service registrations cannot fulfill.

    Key Wrappers:

    • EnumerableWrapper: Handles IEnumerable<>, IList<>, etc.
    • LazyWrapper: Handles Lazy<>.
    • FuncWrapper: Handles Func<> and other non-void delegates.
    • MetadataWrapper: Handles ValueTuple<,>, Tuple<,>, or Metadata<,>.
    • KeyValueWrapper: Handles KeyValuePair<,> or ReadOnlyKeyValue<,>.

    Key Resolvers:

    • ServiceProviderResolver: Resolves the scope as IServiceProvider.
    • OptionalValueResolver: Handles optional parameters.
    • DefaultValueResolver: Handles default values.
    • ParentContainerResolver: Resolves services from parent containers.
    • UnknownTypeResolver: Handles services not registered in the container.
  3. 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 Implementation
  4. Access the resolving type context in factories

    To access metadata about the type currently being resolved (useful for open generics), include TypeInformation as a parameter in the factory delegate. The TypeInformation object 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>>();
  5. 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) 
        { }
    }
  6. Access the resolving type information in a factory

    To access metadata about the type currently being resolved (useful for open generics), include TypeInformation as a parameter in the .WithFactory delegate. The TypeInformation object 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>>();
  7. Implement a custom service lifetime

    Custom lifetimes can be created by inheriting from either ExpressionLifetimeDescriptor (for expression-based instantiation) or FactoryLifetimeDescriptor (for pre-compiled factory delegates). You must override the ApplyLifetime method 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()));
  8. 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>>();
  9. Register multiple types using batch registration

    Use RegisterTypes to register a collection of types to all their implemented interfaces, extended base classes, and themselves (self-registration). By default, framework types like IDisposable are 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)
  10. 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 an AggregateException.
  11. Configure and resolve services with Stashbox

    This example demonstrates how to decouple services using interfaces and configure them within a StashboxContainer in 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();
        }
    }
  12. Register multiple types using batch registration

    Use RegisterTypes to register a collection of types to all their implemented interfaces, extended base classes, and themselves (self-registration). By default, framework types like IDisposable are 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)