Registration configuration
Most of the registration methods have an Action<TOptions>
parameter, enabling several customization options on the given registration.
Here are three examples that show how the API's usage looks like. They cover the exact functionalities you've read about in the basics section but are achieved with the options API.
- Named
- Lifetime
- Instance
This is how you can use the options API to set a registration's name:
container.Register<IJob, DbBackup>(options => options
.WithName("DbBackup"));
It was mentioned in the Lifetime shortcuts section, that those methods are only sugars; under the curtain, they are also using this API:
container.Register<IJob, DbBackup>(options => options
.WithLifetime(Lifetimes.Singleton));
An example of how you can register an instance with the options API:
container.Register<IJob, DbBackup>(options => options
.WithInstance(new DbBackup()));
The registration configuration API is fluent, which means all option methods can be chained after each other. This provides an easier way to configure complicated registrations.
container.Register<IJob, DbBackup>(options => options
.WithName("DbBackup")
.WithLifetime(Lifetimes.Singleton)
.WithoutDisposalTracking());
General options
WithName
Sets the name identifier of the registration.
container.Register<ILogger, ConsoleLogger>(config => config
.WithName("Console"));
WithInstance
Sets an existing instance for the registration.
Passing true for the wireUp
parameter means that the container performs member / method injection on the registered instance.
- Instance
- WireUp
container.Register<ILogger>(options => options
.WithInstance(new ConsoleLogger()));
container.Register<ILogger>(options => options
.WithInstance(new ConsoleLogger(), wireUp: true));
WithoutDisposalTracking
Force disables the disposal tracking on the registration.
container.Register<ILogger, ConsoleLogger>(options => options
.WithoutDisposalTracking());
WithMetadata
Sets additional metadata for the registration. It's attached to the service upon its resolution through ValueTuple<,>
, Tuple<,>
, or Metadata<,>
wrappers.
container.Register<IJob, DbBackup>(options => options
.WithMetadata(connectionString));
var jobWithConnectionString = container.Resolve<ValueTuple<IJob, string>>();
Console.WriteLine(jobWithConnectionString.Item2); // prints the connection string.
WithDynamicResolution
Indicates that the service's resolution should be handled by a dynamic Resolve()
call on the current IDependencyResolver
instead of a pre-built instantiation expression.
container.Register<IJob, DbBackup>();
container.Register<ILogger, ConsoleLogger>(options => options
.WithDynamicResolution());
// new DbBackup(currentScope.Resolve<ILogger>());
var job = container.Resolve<IJob>();
HasServiceType
Used to build conditions based on service type in batch/assembly registrations. It determines whether the registration is mapped to the given service type.
container.RegisterAssemblyContaining<IService1>(configurator: options =>
{
if (options.HasServiceType<IService2>())
options.WithScopedLifetime();
});
Initializer / finalizer
WithFinalizer
Sets a custom cleanup delegate that will be invoked when the scope / container holding the instance is being disposed.
container.Register<ILogger, FileLogger>(options => options
.WithFinalizer(logger => logger
.CloseFile()));
WithInitializer
Sets a custom initializer delegate that will be invoked when the given service is being instantiated.
container.Register<ILogger, FileLogger>(options => options
.WithInitializer((logger, resolver) => logger
.OpenFile()));
Replace
- ReplaceExisting
- ReplaceOnlyIfExists
Indicates whether the container should replace an existing registration with the current one (based on implementation type and name). If there's no existing registration in place, the actual one will be added to the registration list.
container.Register<ILogger, ConsoleLogger>(options => options
.ReplaceExisting());
The same as ReplaceExisting()
except that the container will do the replace only when there's an already registered service with the same type or name.
container.Register<ILogger, ConsoleLogger>(options => options
.ReplaceOnlyIfExists());
Multiple services
You can read more about binding a registration to multiple services here.
AsImplementedTypes
The service will be mapped to all of its implemented interfaces and base types.
container.Register<IUserRepository, UserRepository>(options => options
.AsImplementedTypes());
AsServiceAlso
Binds the currently configured registration to an additional service type. The registered type must implement or extend the additional service type.
container.Register<IUserRepository, UserRepository>(options => options
.AsServiceAlso<IRepository>()
// or
.AsServiceAlso(typeof(IRepository)));
Dependency configuration
These options allows the same configuration functionality as the dependency attribute.
- By parameter type
- By parameter name
- By expression
Binds a constructor / method parameter or a property / field to a named registration by the parameter's type. The container will perform a named resolution on the bound dependency. The second parameter used to set the name of the dependency.
container.Register<IUserRepository, UserRepository>(options => options
.WithDependencyBinding(typeof(ILogger), "FileLogger"));
Binds a constructor / method parameter or a property / field to a named registration by the parameter's name. The container will perform a named resolution on the bound dependency. The second parameter used to set the name of the dependency.
container.Register<IUserRepository, UserRepository>(options => options
.WithDependencyBinding("logger", "FileLogger"));
Marks a member (property / field) as a dependency that should be filled by the container. The second parameter used to set the name of the dependency.
container.Register<IUserRepository, UserRepository>(options => options
.WithDependencyBinding(logger => logger.Logger, "ConsoleLogger"));
Lifetime
You can read more about lifetimes here.
WithSingletonLifetime
Sets a singleton lifetime for the registration.
container.Register<ILogger, ConsoleLogger>(config => config
.WithSingletonLifetime());
WithScopedLifetime
Sets a scoped lifetime for the registration.
container.Register<ILogger, ConsoleLogger>(config => config
.WithScopedLifetime());
WithPerScopedRequestLifetime
Sets the lifetime to PerScopedRequestLifetime
. This lifetime will create a new instance between scoped services. This means that every scoped service will get a different instance but within their dependency tree it will behave as a singleton.
container.Register<ILogger, ConsoleLogger>(options => options
.WithPerScopedRequestLifetime());
WithPerRequestLifetime
Sets the lifetime to PerRequestLifetime
. This lifetime will create a new instance between resolution requests. Within the request the same instance will be re-used.
container.Register<ILogger, ConsoleLogger>(options => options
.WithPerRequestLifetime());
WithAutoLifetime
Sets the lifetime to auto lifetime. This lifetime aligns to the lifetime of the resolved service's dependencies. When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary.
container.Register<ILogger, ConsoleLogger>(options => options
.WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */));
WithLifetime
Sets a custom lifetime for the registration.
container.Register<ILogger, ConsoleLogger>(config => config
.WithLifetime(new CustomLifetime()));
Conditions
You can read more about the concept of conditional resolution here.
WhenHas
Sets an attribute condition for the registration.
container.Register<ILogger, ConsoleLogger>(config => config
.WhenHas<ConsoleAttribute>());
WhenResolutionPathHas
Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the target that has the given attribute. This means that only the direct and sub-dependencies of the target type that has the given attribute will get the configured service.
container.Register<ILogger, ConsoleLogger>(config => config
// Each direct and sub-dependency of any service that has
// a ConsoleAttribute will get FileLogger wherever they
// depend on ILogger.
.WhenResolutionPathHas<ConsoleAttribute>());
WhenDependantIs
Sets a parent target condition for the registration.
container.Register<ILogger, FileLogger>(config => config
.WhenDependantIs<UserRepository>());
WhenInResolutionPathOf
Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the given target. This means that only the direct and sub-dependencies of the target type will get the configured service.
container.Register<ILogger, FileLogger>(config => config
// Each direct and sub-dependency of UserRepository
// will get FileLogger wherever they depend on ILogger.
.WhenInResolutionPathOf<UserRepository>());
When
Sets a custom user-defined condition for the registration.
container.Register<ILogger, FileLogger>(config => config
.When(typeInfo => typeInfo.ParentType == typeof(UserRepository)));
Constructor selection
WithConstructorSelectionRule
Sets the constructor selection rule for the registration.
container.Register<ILogger>(options => options
.WithConstructorSelectionRule(...));
PreferMostParameters
Selects the constructor which has the longest parameter list.
options.WithConstructorSelectionRule(
Rules.ConstructorSelection.PreferMostParameters)
PreferLeastParameters
Selects the constructor which has the shortest parameter list.
options.WithConstructorSelectionRule(
Rules.ConstructorSelection.PreferLeastParameters)
Custom
You can set your own custom constructor ordering logic.
options.WithConstructorSelectionRule(
constructors => { /* custom constructor sorting logic */ })
WithConstructorByArgumentTypes
Selects a constructor by its argument types.
container.Register<IUserRepository, UserRepository>(options => options
.WithConstructorByArgumentTypes(typeof(ILogger)));
WithConstructorByArguments
Selects a constructor by its arguments to use during resolution. These arguments are used to invoke the selected constructor.
container.Register<IUserRepository, UserRepository>(options => options
.WithConstructorByArguments(new ConsoleLogger()));
Property / field Injection
WithAutoMemberInjection
Enables the auto member injection and sets the rule for it.
container.Register<IUserRepository, UserRepository>(options => options
.WithAutoMemberInjection(...));
PropertiesWithPublicSetter
With this flag, the container will perform auto-injection on properties with a public setter.
options.WithAutoMemberInjection(
Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter)
PropertiesWithLimitedAccess
With this flag, the container will perform auto-injection on properties which has a non-public setter as well.
options.WithAutoMemberInjection(
Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess)
PrivateFields
With this flag, the container will perform auto-injection on private fields too.
options.WithAutoMemberInjection(
Rules.AutoMemberInjectionRules.PrivateFields)
options.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields |
Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter)
Member selection filter
You can pass your own member selection logic to control which members should be auto injected.
container.Register<ILogger, ConsoleLogger>(options => options
.WithAutoMemberInjection(filter: member => member.Type != typeof(ILogger)));
Required member injection
With this option, you can enable or disable the auto injection of members defined with C# 11's required
keyword.
container.Register<IUserRepository, UserRepository>(options => options
.WithRequiredMemberInjection(enabled: false));
The required member injection option is enabled by default.
Injection parameters
WithInjectionParameters
Sets multiple injection parameters for the registration.
container.Register<IUserRepository, UserRepository>(options => options
.WithInjectionParameters(new KeyValuePair<string, object>("logger", new ConsoleLogger()));
WithInjectionParameter
Sets a single injection parameter for the registration.
container.Register<IUserRepository, UserRepository>(options => options
.WithInjectionParameter("logger", new ConsoleLogger());
Factory
You can read more about the concept of factory registration here.
- Parameterized
- Parameter-less
- Resolver parameter
WithFactory - Sets a factory delegate that could take various number of pre-resolved dependencies as parameters and returns the service instance.
// 1 parameter factory
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory<ILogger>(logger => new UserRepository(logger));
// 2 parameters factory
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory<ILogger, IDbContext>((logger, context) => new UserRepository(logger, context));
// 3 parameters factory
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory<ILogger, IDbContext, IOptions>((logger, context, options) =>
new UserRepository(logger, context, options));
// 4 parameters factory
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory<ILogger, IDbConnection, IOptions, IUserValidator>((logger, connection, options, validator) =>
new UserRepository(logger, connection, options, validator));
// 5 parameters factory
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory<ILogger, IDbConnection, IOptions, IUserValidator, IPermissionManage>(
(logger, connection, options, validator, permissionManager) =>
new UserRepository(logger, connection, options, validator, permissionManager));
You can also get the current dependency resolver as a pre-resolved parameter:
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory<ILogger, IDependencyResolver>((logger, resolver) =>
new UserRepository(logger, resolver.Resolve<IDbConnection>())));
WithFactory - Sets a parameter-less factory delegate that returns the service instance.
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory(()) => new UserRepository(new ConsoleLogger()));
WithFactory - Sets a factory delegate that takes an IDependencyResolver
as parameter and returns the service instance.
container.Register<IUserRepository, UserRepository>(options => options
.WithFactory(resolver => new UserRepository(resolver.Resolve<ILogger>()));
All factory configuration method has an isCompiledLambda
parameter which should be set to true
if the passed delegate is compiled from an Expression
tree.
Scope definition
You can read more about the concept of defined scopes here.
InNamedScope
Sets a scope name condition for the registration; it will be used only when a scope with the same name requests it.
container.Register<IUserRepository, UserRepository>(options => options
.InNamedScope("UserRepo"));
InScopeDefinedBy
Sets a condition for the registration; it will be used only within the scope defined by the given type.
container.Register<ILogger, ConsoleLogger>(options => options
.InScopeDefinedBy<UserRepository>());
container.Register<IUserRepository, UserRepository>(options => options
.DefinesScope());
DefinesScope
This registration is used as a logical scope for it's dependencies. Dependencies registered with InNamedScope()
with the same name are preferred during resolution.
When the name
is not set, the service type is used as the name. Dependencies registered with InScopeDefinedBy()
are selected.
container.Register<IUserRepository, UserRepository>(options => options
.DefinesScope("UserRepo"));
// or
container.Register<IUserRepository, UserRepository>(options => options
.DefinesScope());
Decorator specific
You can read more about decorators here.
WhenDecoratedServiceIs
Sets a decorated target condition for the registration.
container.RegisterDecorator<ILogger, LoggerDecorator>(options => options
.WhenDecoratedServiceIs<FileLogger>());
WhenDecoratedServiceHas
Sets an attribute condition that the decorated target has to satisfy.
container.RegisterDecorator<ILogger, LoggerDecorator>(options => options
.WhenDecoratedServiceHas<DetailedLoggingAttribute>());
Unknown registration specific
You can read more about unknown type resolution here.
SetImplementationType
Sets the current registration's implementation type.
var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config =>
{
if (config.ServiceType == typeof(IService))
config.SetImplementationType(typeof(Service));
}));
Skip
Marks the current unknown type registration as skipped.
var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config =>
{
if (config.ServiceType == typeof(IService))
config.Skip();
}));