diff --git a/.version b/.version
index 3c8ff8c3..084e244c 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-3.5.1
\ No newline at end of file
+3.6.0
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47a9cae1..30def0a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,68 +1,115 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [v3.6.0] - 2021-02-25
+
+[API changes](https://www.fuget.org/packages/Stashbox/3.6.0/lib/netstandard2.1/diff/3.5.1/)
+
+### Added
+- Parameterized factory delegates. [Read more](https://z4kn4fein.github.io/stashbox/#/usage/advanced-registration?id=factory-registration).
+- Multiple conditions from the same type are now combined with **OR** logical operator. [Read more](https://z4kn4fein.github.io/stashbox/#/usage/service-resolution?id=conditional-resolution).
+- Named version of the `.WhenDecoratedServiceIs()` decorator condition. [Read more](https://z4kn4fein.github.io/stashbox/#/advanced/decorators?id=conditional-decoration).
+
+### Deprecated
+- `.InjectMember()` registration configuration option. `.WithDependencyBindig()` should be used instead. [Read more](https://z4kn4fein.github.io/stashbox/#/configuration/registration-configuration?id=dependency-configuration).
+
+### Removed
+- The `GetRegistrationOrDefault(type, resolutionContext, name)` method of the `IRegistrationRepository` interface.
+- Some properties of the `RegistrationContext` class were moved to internal visibility.
+
## [v3.5.1] - 2021-02-19
-- Bugfix: When a singleton registration had been replaced with `.ReplaceExisting()`, the container still used the old instance. #98
+### Fixed
+- When a singleton registration was replaced with `.ReplaceExisting()`, the container still used the old instance. [#98](https://github.com/z4kn4fein/stashbox/issues/98)
## [v3.5.0] - 2021-01-31
+### Added
- Assembly scanning:
- Added option to filter service types and disable self-registration.
- Recognize generic definitions.
-- Added support to covariant/contravariant generic type resolution.
-- Bugfix: Services with named scope lifetime were not choose right from the registration repo.
+- Support to covariant/contravariant generic type resolution.
+
+### Fixed
+- Services with named scope lifetime were not chosen right from the registration repo.
## [v3.4.0] - 2020-11-15
-- Added the core components of multitenant functionality.
+### Added
+- The core components of multitenant functionality.
- Throw `ObjectDisposedException` when the container or scope is used after their disposal.
## [v3.3.0] - 2020-11-05
-- Added the option to rebuild singletons in child container with dependencies overridden in it.
-- Fix: Singleton instances were built when the Validate() was called, now just the expression is generated for them.
+### Added
+- Option to rebuild singletons in child container with dependencies overridden in it.
+
+### Fixed
+- Singleton instances were built when the Validate() was called, now just the expression is generated for them.
## [v3.2.9] - 2020-11-02
-- Added the option to replace a registration only if an existing one is registered with the same type or name.
+### Added
+- Option to replace a registration only if an existing one is registered with the same type or name.
## [v3.2.8] - 2020-10-17
-- Switch to license expression in nuget package.
+### Changed
+- Switch to license expression in nuget package. [#95](https://github.com/z4kn4fein/stashbox/issues/95)
## [v3.2.7] - 2020-10-16
+### Changed
- Minor bugfixes.
## [v3.2.6] - 2020-10-16
+### Added
- The Validate() method now throws an AggregateException containing all the underlying exceptions.
+
+### Changed
- Minor bugfixes.
## [v3.2.5] - 2020-10-12
+
+### Changed
- Minor bugfixes.
## [v3.2.4] - 2020-07-22
-- Added the `.WhenDecoratedServiceHas()` and `.WhenDecoratedServiceIs()` decorator configuration options.
+### Added
+- The `.WhenDecoratedServiceHas()` and `.WhenDecoratedServiceIs()` decorator configuration options.
## [v3.2.2] - 2020-07-21
-- Added support of conditional and lifetime managed decorators #93
+### Added
+- Support of conditional and lifetime managed decorators [#93](https://github.com/z4kn4fein/stashbox/issues/93)
## [v3.2.1] - 2020-07-09
-- Fix: Factory resolution didn't use the built-in expression compiler.
+### Fixed
+- Factory resolution didn't use the built-in expression compiler.
## [v3.2.0] - 2020-06-29
-- Added IAsyncDisposable support #90
+### Added
+- IAsyncDisposable support [#90](https://github.com/z4kn4fein/stashbox/issues/90)
- It works on >=net461, >=netstandard2.0 frameworks.
- On net461 and netstandard2.0 the usage of IAsyncDisposable interface requires the
Microsoft.Bcl.AsyncInterfaces package, on netstandard2.1 it's part of the framework.
-- Fix: resolving with custom parameter values #91
+
+### Fixed
+- Resolution with custom parameter values [#91](https://github.com/z4kn4fein/stashbox/issues/91)
## [v3.1.2] - 2020-06-22
-- Fix: IdentityServer not compatible #88
-- Fix: Call interception #89
+### Fixed
+- IdentityServer not compatible [#88](https://github.com/z4kn4fein/stashbox/issues/88)
+- Call interception [#89](https://github.com/z4kn4fein/stashbox/issues/89)
## [v3.1.1] - 2020-06-11
-- Fix: String constant is not handled well by the built-in compiler #86
-- Fix: Registration behaviour doesn't respect replacing #87
+### Fixed
+- String constant is not handled well by the built-in compiler [#86](https://github.com/z4kn4fein/stashbox/issues/86)
+- Registration behaviour doesn't respect replacing [#87](https://github.com/z4kn4fein/stashbox/issues/87)
## [v3.1.0] - 2020-06-08
-- Fix: Nested named resolution could cause stack overflow #74
-- Fix: Improve support for Assemblies loaded into Collectible AssemblyLoadContexts #73
-- Fix: Unknown type resolution does not work recursively #77
-- Fix: Exception when building expressions #76
-- Fix: Bad performance #79
-- Fix: Expected override behaviour not working with scopes #80
+### Fixed
+- Nested named resolution could cause stack overflow [#74](https://github.com/z4kn4fein/stashbox/issues/74)
+- Improve support for Assemblies loaded into Collectible AssemblyLoadContexts [#73](https://github.com/z4kn4fein/stashbox/issues/73)
+- Unknown type resolution does not work recursively [#77](https://github.com/z4kn4fein/stashbox/issues/77)
+- Exception when building expressions [#76](https://github.com/z4kn4fein/stashbox/issues/76)
+- Bad performance [#79](https://github.com/z4kn4fein/stashbox/issues/79)
+- Expected override behaviour not working with scopes [#80](https://github.com/z4kn4fein/stashbox/issues/80)
### Breaking changes:
- `WithUniqueRegistrationIdentifiers()` option has been removed, `WithRegistrationBehavior()` has been added instead.
@@ -83,6 +130,7 @@
- Removed the legacy container extension functionality.
- Removed the support of PCL v259.
+[v3.6.0]: https://github.com/z4kn4fein/stashbox/compare/3.5.1...3.6.0
[v3.5.1]: https://github.com/z4kn4fein/stashbox/compare/3.5.0...3.5.1
[v3.5.0]: https://github.com/z4kn4fein/stashbox/compare/3.4.0...3.5.0
[v3.4.0]: https://github.com/z4kn4fein/stashbox/compare/3.3.0...3.4.0
diff --git a/README.md b/README.md
index 67cbad40..892d4ffb 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,9 @@ Stashbox is a lightweight, fast and portable dependency injection framework for
Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (daily)
--- | --- | --- | ---
-[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg)](https://www.fuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/)
+[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg?v=3.6.0)](https://www.fuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/)
-## Core attributes
+## Core Attributes
- 🚀 Fast, thread-safe, and lock-free operations.
- ⚡️ Easy-to-use Fluent configuration API.
- ♻️ Small memory footprint.
@@ -19,7 +19,7 @@ Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (daily)
- 🚨 Detects and warns about misconfigurations.
- 🔥 Gives fast feedback on registration/resolution issues.
-## Supported platforms
+## Supported Platforms
- .NET 4.0 and above
- .NET Core
@@ -29,7 +29,7 @@ Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (daily)
- Unity
## Contact & Support
-- [![Join the chat at https://gitter.im/z4kn4fein/stashbox](https://img.shields.io/gitter/room/z4kn4fein/stashbox.svg)](https://gitter.im/z4kn4fein/stashbox) [![Slack](https://img.shields.io/badge/chat-on%20slack-orange.svg?style=flat)](https://3vj.short.gy/stashbox-slack) [![OpenHub](https://www.openhub.net/p/stashbox/widgets/project_thin_badge?format=gif)](https://www.openhub.net/p/stashbox)
+- [![Join the chat at https://gitter.im/z4kn4fein/stashbox](https://img.shields.io/gitter/room/z4kn4fein/stashbox.svg)](https://gitter.im/z4kn4fein/stashbox) [![Slack](https://img.shields.io/badge/chat-on%20slack-orange.svg?style=flat)](https://3vj.short.gy/stashbox-slack)
- Create an [issue](https://github.com/z4kn4fein/stashbox/issues) for bug reports, feature requests, or questions.
- Add a ⭐️ to support the project!
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
index 037aed1e..558168b7 100644
--- a/docs/_coverpage.md
+++ b/docs/_coverpage.md
@@ -1,6 +1,6 @@
![logo](assets/images/icon.png)
-# Stashbox 3.5.1
+# Stashbox 3.6.0
> A lightweight, fast, and portable .NET DI framework.
diff --git a/docs/_glossary.md b/docs/_glossary.md
index 7fd33560..c497b0ac 100644
--- a/docs/_glossary.md
+++ b/docs/_glossary.md
@@ -1,7 +1,7 @@
# Glossary
The following definitions are used in the documentation.
-## Service type | Implementation type
+## Service Type | Implementation Type
The *Service type* is usually an interface or an abstract class type used for service resolution or dependency injection. The *Implementation type* is the actual type registered to the *Service type*. A registration maps the *Service type* to an *Implementation type*. The *Implementation type* must implement or extend the *Service type*.
@@ -19,7 +19,7 @@ container.Resolve(); // returns Implementation
```
-## Service registration | Registered service
+## Service Registration | Registered Service
It's an entity created by the container when a service is registered. The service registration stores required information about how to instantiate the service, e.g., reflected type information, name, lifetime, conditions, and more.
@@ -34,7 +34,7 @@ var service = container.Resolve("Example");
```
-## Injectable dependency
+## Injectable Dependency
@@ -51,7 +51,7 @@ class Implementation : IService
```
-## Resolution tree
+## Resolution Tree
It's the structural representation of how the container resolves a service's dependencies for instantiation.
Let's see through an example:
@@ -79,17 +79,17 @@ When we request the service `A`, the container builds up the following resolutio
```
The container instantiates those services first that don't have any dependencies. `C` and `D` will be injected into `B`. Then, a new `C` is instantiated (if it's [transient](usage/lifetimes?id=transient-lifetime)) and injected into `A` along with the previously created `B`.
-## Dependency resolver
-It's the container itself or the [current scope](usage/scopes), depending on which was requested to resolve a particular service. They are both implementing Stashbox's `IDependencyResolver` and the .NET framework's `IServiceProvider` interface and can be used for resolution purposes.
+## Dependency Resolver
+It's the container itself or the [current scope](usage/scopes), depending on which was requested to resolve a particular service. They are both implementing Stashbox's `IDependencyResolver` and the .NET framework's `IServiceProvider` interface and can be used for service resolution purposes.
-?> Stashbox implicitly injects the [current scope](usage/scopes) everywhere `IDependencyResolver` or `IServiceProvider` is requested.
+?> Stashbox implicitly injects the [current scope](usage/scopes) wherever `IDependencyResolver` or `IServiceProvider` is requested.
-## Root scope
-It's the [main scope](usage/scopes) created inside every container instance. It stores and handles the lifetime of all singletons. It's the base of all subsequent scopes created by a container with the `.BeginScope()` method.
+## Root Scope
+It's the [main scope](usage/scopes) created inside every container instance. It stores and handles the lifetime of all singletons. It's the base of all subsequent scopes created by the container with the `.BeginScope()` method.
!> [Scoped services](usage/lifetimes?id=scoped-lifetime) requested from the container (and not from a [scope](usage/scopes)) will be managed by the root scope. This can lead to issues because their lifetime will switch to singleton. Always be sure that you are not resolving scoped services directly from the container, only from a [scope](usage/scopes).
-## Named resolution
+## Named Resolution
@@ -97,7 +97,6 @@ It's a resolution request for a named service. The same applies, when the contai
```cs
container.Register("Example");
-
// the named resolution initiated by request.
var service = container.Resolve("Example");
```
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index b9f93cf3..e7868975 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -1,28 +1,28 @@
- **Getting started**
- [Overview](getting-started/overview)
-- [Quick start](getting-started/quick-start)
+- [Quick Start](getting-started/quick-start)
- [Glossary](_glossary)
- **Using Stashbox**
-- [Basic usage](usage/basics)
-- [Advanced registration](usage/advanced-registration)
-- [Service resolution](usage/service-resolution)
+- [Basic Usage](usage/basics)
+- [Advanced Registration](usage/advanced-registration)
+- [Service Resolution](usage/service-resolution)
- [Scopes](usage/scopes)
- [Lifetimes](usage/lifetimes)
- **Configuration**
-- [Registration configuration](configuration/registration-configuration)
-- [Container configuration](configuration/container-configuration)
+- [Registration Configuration](configuration/registration-configuration)
+- [Container Configuration](configuration/container-configuration)
- **Advanced topics**
- [Generics](advanced/generics)
- [Decorators](advanced/decorators)
- [Resolvers](advanced/resolvers)
-- [Child container](advanced/child-container)
+- [Child Containers](advanced/child-containers)
+- [Special Resolution Cases](advanced/special-resolution-cases)
- **Diagnostics**
- [Validation](diagnostics/validation)
-- [Exceptions](diagnostics/exceptions)
- [Utilities](diagnostics/utilities)
- **Links**
- [Slack](https://3vj.short.gy/stashbox-slack)
- [Gitter](https://gitter.im/z4kn4fein/stashbox)
- [GitHub](https://github.com/z4kn4fein/stashbox)
- [NuGet](https://www.nuget.org/packages/Stashbox/)
-- [API documentation](https://www.fuget.org/packages/Stashbox/)
\ No newline at end of file
+- [API Documentation](https://www.fuget.org/packages/Stashbox/)
\ No newline at end of file
diff --git a/docs/advanced/child-container.md b/docs/advanced/child-containers.md
similarity index 61%
rename from docs/advanced/child-container.md
rename to docs/advanced/child-containers.md
index 375ad902..a1178f95 100644
--- a/docs/advanced/child-container.md
+++ b/docs/advanced/child-containers.md
@@ -1,17 +1,9 @@
-# Child container
-With child containers, you can build up parent-child relationships between containers. It means you can have a different subset of services present in child and parent containers. When something is missing from a child container during a resolution request, the parent will be asked to resolve the missing service.
+# Child Containers
+With child containers, you can build up parent-child relationships between containers. It means you can have a different subset of services present in a child than the parent container. When a dependency is missing from the child container during a resolution request, the parent will be asked to resolve the missing service. If it's found there, the parent will return only the service's registration, and the resolution request will continue within the child. Also, child registrations with the same service type will override the parent's services.
## Example
-Here are the actions of an example case:
-1. Resolution request on *child* container for `A`.
-2. `A` not found in the *child*, go up to the *parent* and check there.
-3. `A` found in the *parent*, resolve.
-4. `A` is depending on `B`, go back to the *child* and search `B`.
-5. `B` found in the *child*, resolve.
-6. All dependencies are resolved; return `A`.
-
-Let's see this with code:
+Here is an example case:
```cs
interface IDependency {}
@@ -26,10 +18,10 @@ class A
using (var container = new StashboxContainer())
{
- // register 'A' into the main container.
+ // register 'A' into the parent container.
container.Register();
- // register 'B' as a dependency into the main container.
+ // register 'B' as a dependency into the parent container.
container.Register();
using (var child = container.CreateChildContainer())
@@ -40,17 +32,27 @@ using (var container = new StashboxContainer())
// 'A' is resolved from the parent and gets
// 'C' as IDependency because the resolution
// request was initiated on the child.
- child.Resolve();
+ A fromChild = child.Resolve();
}
// 'A' gets 'B' as IDependency because the
- // resolution request was initiated on the main.
- container.Resolve();
+ // resolution request was initiated on the parent.
+ A fromParent = container.Resolve();
}
```
+Let's see what's happening when we request `A` from the *child*:
+1. `A` not found in the *child*, go up to the *parent* and check there.
+2. `A` found in the *parent*, resolve.
+3. `A` depends on `IDependency`, go back to the *child* and search `IDependency` implementations.
+4. `C` found in the *child*, it does not have any dependencies, instantiate.
+5. Inject the new `C` instance into `A`.
+5. All dependencies are resolved; return `A`.
+
+When we make the same request on the parent, everything will go as usual because we have all dependencies in place. `B` will be injected into `A`.
+
?> You can [re-configure](configuration/container-configuration) child containers with the `.Configure()` method. It doesn't affect the parent container's configuration.
-## Re-building singletons
+## Re-building Singletons
By default, singletons are instantiated and stored only in those containers that registered them. However, you can enable the re-instantiation of singletons in child containers with the `.WithReBuildSingletonsInChildContainer()` [container configuration option](configuration/container-configuration?id=re-build-singletons-in-child-containers).
If it's enabled, all singletons will be re-created within those containers that initiated the resolution request. It means that re-built singletons can use overridden dependencies from child containers.
@@ -71,15 +73,15 @@ class A
using (var container = new StashboxContainer(options => options.WithReBuildSingletonsInChildContainer()))
{
- // register 'A' as a singleton into the main container.
+ // register 'A' as a singleton into the parent container.
container.RegisterSingleton();
- // register 'B' as a dependency into the main container.
+ // register 'B' as a dependency into the parent container.
container.Register();
// 'A' gets 'B' as IDependency and will be stored
- // in the main container as a singleton.
- container.Resolve();
+ // in the parent container as a singleton.
+ A fromParent = container.Resolve();
using (var child = container.CreateChildContainer())
{
@@ -88,7 +90,7 @@ using (var container = new StashboxContainer(options => options.WithReBuildSingl
// a new 'A' singleton will be created in
// the child container with 'C' as IDependency
- child.Resolve();
+ A fromChild = child.Resolve();
}
// using will dispose the child and the
// newly created singleton instance of 'A'
@@ -97,12 +99,12 @@ using (var container = new StashboxContainer(options => options.WithReBuildSingl
// original singleton instance of 'A'
```
-## Nested child containers
+## Nested Child Containers
-You can build up a hierarchical tree structure from containers as a child can create other child containers with the `.CreateChildContainer()` method.
+You can build up a hierarchical tree structure from child containers because they can create other child containers with the `.CreateChildContainer()` method.
?> This feature is the core of the [multi-tenant package](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#multitenant).
diff --git a/docs/advanced/decorators.md b/docs/advanced/decorators.md
index d02f3822..cb0adf70 100644
--- a/docs/advanced/decorators.md
+++ b/docs/advanced/decorators.md
@@ -1,7 +1,7 @@
# Decorators
Stashbox supports decorator service registration to take advantage of the [Decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern). This pattern is used to extend the functionality of a class without changing its implementation. This is also what the [Open–closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) stands for; services should be open for extension but closed for modification.
-## Simple use-case
+## Simple Use-Case
We define an `IEventProcessor` service used to process `Event` entities. Then we'll decorate this service with additional validation capabilities:
```cs
class Event { }
@@ -65,7 +65,7 @@ eventProcessor.ProcessEvent(new UpdateEvent());
```
The `GeneralEventProcessor` is an implementation of `IEventProcessor` and does the actual event processing logic. It does not have any other responsibilities. Rather than putting the event validation's burden onto its shoulder, we create a different service for validation purposes. Instead of injecting the validator into the `GeneralEventProcessor` directly, we let another `IEventProcessor` decorate it like an *event processing pipeline* that validates the event as a first step.
-## Multiple decorators
+## Multiple Decorators
@@ -86,7 +86,7 @@ var processor = container.Resolve();
-## Conditional decoration
+## Conditional Decoration
With [conditional resolution](usage/service-resolution?id=conditional-resolution) you can control which decorator should be selected to decorate a given service.
@@ -100,7 +100,8 @@ container.Register();
container.RegisterDecorator(options => options
// select when CustomProcessor or GeneralProcessor is resolved.
- .When(typeInfo => typeInfo.Type == typeof(CustomProcessor) || typeInfo.Type == typeof(GeneralProcessor)));
+ .WhenDecoratedServiceIs()
+ .WhenDecoratedServiceIs());
container.RegisterDecorator(options => options
// select only when GeneralProcessor is resolved.
@@ -121,11 +122,12 @@ container.Register("Custom");
container.RegisterDecorator(options => options
// select when CustomProcessor or GeneralProcessor is resolved.
- .When(typeInfo => typeInfo.DependencyName.Equals("General") || typeInfo.Type == typeInfo.DependencyName.Equals("Custom")));
+ .WhenDecoratedServiceIs("General")
+ .WhenDecoratedServiceIs("Custom"));
container.RegisterDecorator(options => options
// select only when GeneralProcessor is resolved.
- .When(typeInfo => typeInfo.DependencyName.Equals("General")));
+ .WhenDecoratedServiceIs("General"));
// new ValidatorProcessor(new LoggerProcessor(new GeneralProcessor()))
var general = container.Resolve("General");
@@ -193,7 +195,7 @@ var executor = container.ResolveAll();
-## Generic decorators
+## Generic Decorators
Stashbox supports the registration of open-generic decorators, which allows the extension of open-generic services.
Inspection of [generic parameter constraints](advanced/generics?id=generic-constraints) and [variance handling](advanced/generics?id=variance) is supported on generic decorators also.
@@ -278,7 +280,7 @@ var processor = container.Resolve>();
## Interception
-From the combination of the decorator support and the proxy generator of [Castle DynamicProxy](http://www.castleproject.org/projects/dynamicproxy/) we can take advantage of the benefits of [Aspect-Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming). The following example defines a `LoggingInterceptor` that will log additional messages related to the called service methods.
+From the combination of Stashbox's decorator support and [Castle DynamicProxy's](http://www.castleproject.org/projects/dynamicproxy/) proxy generator, we can take advantage of the [Aspect-Oriented Programming's](https://en.wikipedia.org/wiki/Aspect-oriented_programming) benefits. The following example defines a `LoggingInterceptor` that will log additional messages related to the called service methods.
```cs
public class LoggingInterceptor : IInterceptor
diff --git a/docs/advanced/generics.md b/docs/advanced/generics.md
index a003b5c4..17e72b35 100644
--- a/docs/advanced/generics.md
+++ b/docs/advanced/generics.md
@@ -4,7 +4,7 @@ This section is about how Stashbox supports various usage scenarios that involve
-## Closed-generics
+## Closed-Generics
Registering and resolving a closed-generic type is not different from registering a simple non-generic service.
@@ -29,7 +29,7 @@ object validator = container.Resolve(typeof(IValidator));
-## Open-generics
+## Open-Generics
Registering an open-generic type is different from registering a closed-generic one as the language itself doesn't allow open-generic types in generic method parameters. We have to get a runtime type from the open-generic type first with `typeof()`.
@@ -77,7 +77,7 @@ IValidator validator = container.Resolve>();
-## Generic constraints
+## Generic Constraints
In the following examples, you can see how the container handles generic constraints during service resolution. Constraints can be used for [conditional resolution](usage/service-resolution?id=conditional-resolution) including collection filters.
diff --git a/docs/advanced/resolvers.md b/docs/advanced/resolvers.md
index 1586702b..13e61d8a 100644
--- a/docs/advanced/resolvers.md
+++ b/docs/advanced/resolvers.md
@@ -1,6 +1,6 @@
# Resolvers
-Stashbox is using so-called *Resolver* implementations to handle special resolution requests. [Wrappers](advanced/generics?id=wrappers), unknown type resolution, child-container requests, optional/default value injection; these are all handled by *Resolvers*.
+Stashbox is using so-called *Resolver* implementations to handle special resolution requests. [Wrappers](advanced/generics?id=wrappers), [unknown type resolution](advanced/special-resolution-cases?id=unknown-type-resolution), [cross-container requests](advanced/child-containers), [optional](advanced/special-resolution-cases?id=optional-value-injection)/[default](advanced/special-resolution-cases?id=default-value-injection) value injection; these are all handled by *Resolvers*.
## Pre-defined Resolvers
* `EnumerableResolver`: Used to resolve every registered implementation of a service.
@@ -12,7 +12,7 @@ Stashbox is using so-called *Resolver* implementations to handle special resolut
* `UnknownTypeResolver`: Used to resolve services that are not registered into the container.
* `ParentContainerResolver`: Used to resolve services that are only registered in one of the parent containers.
-## User-defined Resolvers
+## User-Defined Resolvers
You can implement your resolver to extend the functionality of the container.
```cs
class CustomResolver : IResolver
@@ -40,7 +40,7 @@ Then you can register your custom resolver like:
container.RegisterResolver(new CustomResolver());
```
-## Visiting order
+## Visiting Order
Stashbox visits the resolvers in the following order to fulfill the current resolution request:
1. `EnumerableResolver`
diff --git a/docs/advanced/special-resolution-cases.md b/docs/advanced/special-resolution-cases.md
new file mode 100644
index 00000000..b3092863
--- /dev/null
+++ b/docs/advanced/special-resolution-cases.md
@@ -0,0 +1,85 @@
+# Special Resolution Cases
+
+## Unknown Type Resolution
+When this [feature](configuration/container-configuration?id=unknown-type-resolution) is enabled, the container will try to resolve unregistered types by registering them using a pre-defined configuration delegate.
+
+
+#### **Default**
+Without a registration configuration, the container can resolve only non-interface and non-abstract unknown types. In this case,
+the container creates an implicit registration for `Dependency` and injects its instance into `Service`.
+```cs
+class Dependency { }
+
+class Service
+{
+ public Service(Dependency dependency)
+ { }
+}
+
+var container = new StashboxContainer(config => config
+ .WithUnknownTypeResolution());
+
+container.Register();
+
+var service = container.Resolve();
+```
+
+#### **With registration configuration**
+With a registration configuration, you can control how the individual registrations of the unknown types should behave. You also have the option to react to a service resolution request. In this case, we tell the container that if it finds an unregistered `IDependency` service for the first time, it should be mapped to the `Dependency` implementation and have a singleton lifetime. Next time, when the container is coming across with this service, it will use the registration created at the first request.
+```cs
+interface IDependency { }
+
+class Dependency : IDependency { }
+
+class Service
+{
+ public Service(IDependency dependency)
+ { }
+}
+
+var container = new StashboxContainer(config => config
+ .WithUnknownTypeResolution(options =>
+ {
+ if(options.ServiceType == typeof(IDependency))
+ {
+ options.SetImplementationType(typeof(Dependency))
+ .WithLifetime(Lifetimes.Singleton);
+ }
+ }));
+
+container.Register();
+
+var service = container.Resolve();
+```
+
+
+## Default Value Injection
+When this [feature](configuration/container-configuration?id=default-value-injection) is enabled, the container will resolve unknown primitive dependencies with their default value.
+```cs
+class Person
+{
+ public Person(string name, int age) { }
+}
+
+var container = new StashboxContainer(config => config
+ .WithDefaultValueInjection());
+// the name parameter will be null and the age will be 0.
+var person = container.Resolve();
+```
+
+?> Unknown reference types will be resolved to `null` only in properties and fields.
+
+## Optional Value Injection
+Stashbox respects the optional value of constructor and method arguments.
+
+```cs
+class Person
+{
+ public Person(string name = null, int age = 54, IContact contact = null) { }
+}
+
+// the name will be null
+// the age will be 54.
+// the contact will be null.
+var person = container.Resolve();
+```
\ No newline at end of file
diff --git a/docs/assets/css/main.css b/docs/assets/css/main.css
index 991a46d4..a5a0e75c 100644
--- a/docs/assets/css/main.css
+++ b/docs/assets/css/main.css
@@ -2,6 +2,9 @@
--search-margin: 1em 0;
--search-result-keyword-padding: 0 0;
+ --base-color: #c9d1d9;
+ --heading-color: #e4e8ec;
+
--theme-color: #2582ad;
--theme-2-color: #fd8d68;
@@ -12,9 +15,6 @@
--cover-button-box-shadow--hover: 0 5px 16px rgba(3, 26, 39, 0.4);
--sidebar-toggle-icon-stroke-width: 2px;
- /* --sidebar-toggle-offset-top: calc(100vh - (var(--sidebar-toggle-height) / 2) - 60px); */
-
- --heading-h3-font-weight: 400;
--base-font-size: 14px;
@@ -144,6 +144,16 @@ button.sidebar-toggle {
left: auto;
}
+.docsify-copy-code-button {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Old versions of Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome, Edge, Opera and Firefox */
+}
+
::selection {
color: white !important;
}
@@ -162,7 +172,7 @@ button.sidebar-toggle {
}
.markdown-section {
- padding: 5em 0 0 0 !important;
+ padding: 6em 0 0 0 !important;
}
.app-nav {
@@ -193,8 +203,8 @@ button.sidebar-toggle {
}
.markdown-section {
- padding: 5em 0.5em 0 0.8em;
- padding-top: 5em !important;
+ padding: 6em 0.5em 0 0.8em;
+ padding-top: 6em !important;
}
}
\ No newline at end of file
diff --git a/docs/configuration/container-configuration.md b/docs/configuration/container-configuration.md
index c3c57be2..78f1cd25 100644
--- a/docs/configuration/container-configuration.md
+++ b/docs/configuration/container-configuration.md
@@ -1,4 +1,4 @@
-# Container configuration API
+# Container Configuration API
@@ -22,7 +22,16 @@ var container = new StashboxContainer();
container.Configure(options => options.WithDisposableTransientTracking());
```
-## Tracking disposable transients
+
+## Default Configuration
+These features are set or enabled by default:
+
+- [Constructor selection](configuration/container-configuration?id=constructor-selection): `Rules.ConstructorSelection.PreferMostParameters`
+- [Registration behavior](configuration/container-configuration?id=registration-behavior): `Rules.RegistrationBehavior.SkipDuplications`
+- [Default lifetime](configuration/container-configuration?id=default-lifetime): `Lifetimes.Transient`
+
+
+## Tracking Disposable Transients
With this option, you can enable or disable the tracking of disposable transient objects.
@@ -31,7 +40,7 @@ With this option, you can enable or disable the tracking of disposable transient
new StashboxContainer(options => options.WithDisposableTransientTracking());
```
-## Auto member-injection
+## Auto Member-Injection
With this option, you can enable or disable the auto member-injection without [attributes](usage/service-resolution?id=attributes).
@@ -66,7 +75,7 @@ new StashboxContainer(options => options
### Combined rules
-You can also combine these rules also.
+You can also combine these flags with bitwise logical operators to get a merged ruleset.
```cs
new StashboxContainer(options => options
@@ -77,12 +86,12 @@ new StashboxContainer(options => options
?> Member selection filter: `config.WithAutoMemberInjection(filter: member => member.Type != typeof(IJob))`
-## Constructor selection
-With this option, you can set the constructor selection rule used to determine which constructor should the container use for instantiation.
+## Constructor Selection
+With this option, you can set the constructor selection rule used to determine which constructor the container should use for instantiation.
### PreferMostParameters
-Prefers the constructor which has the most extended parameter list.
+It prefers the constructor which has the most extended parameter list.
```cs
new StashboxContainer(options => options
@@ -92,14 +101,14 @@ new StashboxContainer(options => options
### PreferLeastParameters
-Prefers the constructor which has the shortest parameter list.
+It prefers the constructor which has the shortest parameter list.
```cs
new StashboxContainer(options => options
.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters));
```
-## Registration behavior
+## Registration Behavior
With this option, you can set the actual behavior used when a new service is registered into the container. These options do not affect named registrations.
@@ -114,7 +123,7 @@ new StashboxContainer(options => options
### ThrowException
-The container throws an [exception](diagnostics/exceptions?id=servicealreadyregisteredexception) when the given implementation type is already registered.
+The container throws an [exception](diagnostics/validation?id=servicealreadyregisteredexception) when the given implementation type is already registered.
```cs
new StashboxContainer(options => options
@@ -142,7 +151,7 @@ new StashboxContainer(options => options
```
-## Default lifetime
+## Default Lifetime
With this option, you can set the default lifetime used when a service doesn't have a configured one.
@@ -152,7 +161,7 @@ new StashboxContainer(options => options.WithDefaultLifetime(Lifetimes.Scoped));
```
-## Lifetime validation
+## Lifetime Validation
With this option, you can enable or disable the life-span and root scope resolution [validation](diagnostics/validation?id=lifetime-validation) on the dependency tree.
@@ -162,7 +171,7 @@ new StashboxContainer(options => options.WithLifetimeValidation());
```
-## Conventional resolution
+## Conventional Resolution
With this option, you can enable or disable conventional resolution, which means the container treats the constructor/method parameter or member names as dependency names used by named resolution.
@@ -172,7 +181,7 @@ new StashboxContainer(options => options
.TreatParameterAndMemberNameAsDependencyName());
```
-## Using named service for un-named requests
+## Using Named Service for Un-named Requests
With this option, you can enable or disable the selection of named registrations when the resolution request is un-named but with the same type.
@@ -183,7 +192,7 @@ new StashboxContainer(options => options
```
-## Circular dependencies in delegates
+## Circular Dependencies in Delegates
With this option, you can enable or disable the runtime circular dependency tracking.
@@ -195,7 +204,7 @@ new StashboxContainer(options => options.WithRuntimeCircularDependencyTracking()
!> By default, the container checks for circular dependencies when it builds the expression graph, but this could not prevent stack overflows when factory delegates passed by the user are containing circular dependencies. If you turn this feature on, the container will generate nodes into the expression tree that tracks the entering and exiting resolution calls across user-defined factory delegates.
-## Circular dependencies with Lazy
+## Circular Dependencies with Lazy
With this option, you can enable or disable circular dependencies through `Lazy<>` objects.
@@ -205,7 +214,7 @@ new StashboxContainer(options => options.WithCircularDependencyWithLazy());
```
-## Default value injection
+## Default Value Injection
With this option, you can enable or disable the default value injection.
@@ -215,7 +224,7 @@ new StashboxContainer(options => options.WithDefaultValueInjection());
```
-## Unknown type resolution
+## Unknown Type Resolution
With this option, you can enable or disable the resolution of unregistered types. You can also use a configurator delegate to configure the registrations the container will create from the unknown types.
@@ -226,7 +235,7 @@ new StashboxContainer(options => options
```
-## Custom compiler
+## Custom Compiler
With this option, you can set an external expression tree compiler. It can be useful on platforms where the IL generator modules are not available; therefore, the expression compiler in Stashbox couldn't work.
@@ -237,13 +246,14 @@ new StashboxContainer(options => options
```
-## Re-build singletons in child containers
+## Re-build Singletons in Child Containers
-With this option, you can enable or disable the re-building of singletons in child containers. It allows the child containers to effectively override singleton dependencies in the parent. This feature is not affecting the already built singleton instances in the parent.
+With this option, you can enable or disable the re-building of singletons in child containers. It allows the child containers to override singleton dependencies in the parent.
```cs
-new StashboxContainer(options => options
- .WithReBuildSingletonsInChildContainer());
+new StashboxContainer(options => options.WithReBuildSingletonsInChildContainer());
```
-
\ No newline at end of file
+
+
+?> This feature is not affecting the already built singleton instances in the parent.
\ No newline at end of file
diff --git a/docs/configuration/registration-configuration.md b/docs/configuration/registration-configuration.md
index 68884fdc..ace6e206 100644
--- a/docs/configuration/registration-configuration.md
+++ b/docs/configuration/registration-configuration.md
@@ -1,4 +1,4 @@
-# Registration configuration API
+# Registration Configuration API
@@ -48,7 +48,7 @@ container.Register(options => options
-## General options
+## General Options
- **WithName(object name)** - Sets the name of the registration.
```cs
container.Register(config => config.WithName("Console"));
@@ -101,17 +101,25 @@ container.Register(options => options
?> The registered type must implement or extend the additional service type.
-## Dependency configuration
-- **WithDependencyBinding(Type dependencyType, object dependencyName)** - Binds a constructor/method parameter to a named registration by the parameter's type. The container will perform a named resolution on the bound dependency.
+## Dependency Configuration
+- **WithDependencyBinding(Type dependencyType, object dependencyName)** - 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.
```cs
container.Register(options => options.WithDependencyBinding(typeof(ILogger), "FileLogger"));
```
-- **WithDependencyBinding(string parameterName, object dependencyName)** - Binds a constructor/method parameter to a named registration by the parameter's name. The container will perform a named resolution on the bound dependency.
+- **WithDependencyBinding(string parameterName, object dependencyName)** - 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.
```cs
container.Register(options => options.WithDependencyBinding("logger", "FileLogger"));
```
+- **WithDependencyBinding(Expression propertyAccessor, object dependencyName = null)** - Marks a member (property/field) as a dependency that should be filled by the container.
+ ```cs
+ container.Register(options => options.WithDependencyBinding(logger => logger.Logger, "ConsoleLogger"));
+ ```
+
+ ?> The second parameter used to set the name of the dependency.
+
+
## Lifetime
- **WithLifetime(ILifetime lifetime)** - Sets a custom lifetime for the registration.
```cs
@@ -153,7 +161,7 @@ container.Register(options => options
.When(typeInfo => typeInfo.ParentType == typeof(UserRepository)));
```
-## Constructor selection
+## Constructor Selection
- **WithConstructorSelectionRule(Func, IEnumerable> rule)** - Sets the constructor selection rule for the registration.
```cs
container.Register(options => options.WithConstructorSelectionRule());
@@ -182,7 +190,7 @@ container.Register(options => options
.WithConstructorByArguments(new ConsoleLogger()));
```
-## Property/field injection
+## Property/Field Injection
- **WithAutoMemberInjection(AutoMemberInjectionRules rule)** - Enables the auto member injection and sets the rule for it.
```cs
container.Register(options => options.WithAutoMemberInjection(...));
@@ -214,18 +222,7 @@ container.Register(options => options
?> With the filter above, the container will exclude all the class members with the type `ILogger` from auto injection.
-- **InjectMember(string memberName, object dependencyName = null)** - Marks a member (property/field) as a dependency, which should be filled by the container.
- ```cs
- container.Register(options => options.InjectMember("Logger", "ConsoleLogger"));
- ```
-- **InjectMember(Expression propertyAccessor, object dependencyName = null)** - Marks a member (property/field) as a dependency that should be filled by the container.
- ```cs
- container.Register(options => options.InjectMember(logger => logger.Logger, "ConsoleLogger"));
- ```
-
- ?> The second parameter used to set the name of the dependency.
-
-## Injection parameters
+## Injection Parameters
- **WithInjectionParameters(params KeyValuePair[] injectionParameters)** - Sets injection parameters for the registration.
```cs
container.Register(options =>
@@ -243,9 +240,9 @@ container.Register(options => options
.WithFactory(resolver => new UserRepository(resolver.Resolve("FileLogger")));
```
- ?> You can use a parameterless delegate as well: `.WithFactory(() => new ConsoleLogger());`
+ ?> You can use a parameter-less delegate as well: `.WithFactory(() => new ConsoleLogger());`
-## Scope definition
+## Scope Definition
- **InNamedScope(object scopeName)** - Sets a scope name condition for the registration, it will be used only when a scope with the same name requests it.
```cs
container.Register(options => options.InNamedScope("UserRepo"));
diff --git a/docs/diagnostics/exceptions.md b/docs/diagnostics/exceptions.md
deleted file mode 100644
index 6da91d9c..00000000
--- a/docs/diagnostics/exceptions.md
+++ /dev/null
@@ -1,139 +0,0 @@
-# Exceptions
-
-When something goes wrong with the internal state of the container, it throws a specific exception to let you know where the issue lies.
-
-## ResolutionFailedException
-This exception could pop up:
-- **When a dependency is missing from the resolution tree**:
- ```cs
- class Service
- {
- public Service(Dependency dep) { }
-
- public Service(Dependency2 dep2) { }
- }
-
- container.Register().Resolve();
- ```
- This will result a `ResolutionFailedException` with the following message:
-
- ```
- Could not resolve type Namespace.Service.
- Constructor Void .ctor(Dependency) found with unresolvable parameter: (Namespace.Dependency)dep.
- Constructor Void .ctor(Dependency2) found with unresolvable parameter: (Namespace.Dependency2)dep2.
- ```
-
-- **When the requested type is unresolvable**:
- When your top-level resolution request points to a service which is not resolvable (e.g. not registered), the following exception message shows:
-
- ```
- Could not resolve type Namespace.Service.
- Service is not registered or unresolvable type requested.
- ```
-
-- **When a member is unresolvable**:
- Let's see what happens when we are facing this issue with the following type:
- ```cs
- class Service
- {
- public Dependency Dep { get; set; }
- }
-
- container.Register(c => c.InjectMember(s => s.Dep)).Resolve();
- ```
- In this case, we'll get the following message:
-
- ```
- Could not resolve type Namespace.Service.
- Unresolvable property: (Namespace.Dependency)Dep.
- ```
-
-## CircularDependencyException
-When this exception occurs that means the container noticed an infinite dependency loop in the resolution tree.
-```cs
-class Service1
-{
- public Service1(Service2 service2) { }
-}
-
-class Service2
-{
- public Service2(Service1 service1) { }
-}
-
-container.Register().Register().Resolve();
-```
-The exception message is:
-```
-Circular dependency detected during the resolution of Namespace.Service1.
-```
-
-## CompositionRootNotFoundException
-This exception pops up when we try to compose an assembly but it doesn't contain an `ICompositionRoot` implementation.
-```cs
-container.ComposeAssembly(typeof(Service).Assembly);
-```
-The exception message is:
-```
-No ICompositionRoot found in the given assembly: {your-assembly-name}
-```
-
-## ConstructorNotFoundException
-During the registration phase when you are using the `WithConstructorByArgumentTypes()` or `WithConstructorByArguments()` options, you can accidentally point to a non-existing constructor and in that case, the container throws a `ConstructorNotFoundException`.
-
-```cs
-class Service
-{
- public Service(Dependency dep) { }
-}
-
-container.Register(c => c.WithConstructorByArgumentTypes(typeof(string), typeof(int)));
-```
-The exception message is:
-```
-Constructor not found for Namespace.Service with the given argument types: System.String, System.Int32.
-```
-
-## ServiceAlreadyRegisteredException
-When you are registering a service that's already registered and the `RegistrationBehavior` [option of the container](configuration/container-configuration?id=registration-behavior) is set to `ThrowException` it'll throw a `ServiceAlreadyRegisteredException`.
-```cs
-var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.ThrowException));
-container.Register().Register();
-```
-The exception message is:
-```
-The type Namespace.Service is already registered.
-```
-
-## InvalidRegistrationException
-During the service registration, the container executes validation rules on the service types and if the validation fails, it throws an `InvalidRegistrationException`.
-It could show the following messages:
-- When the implementation does not implement the service type like `Register(typeof(ICar), typeof(MotorCycle))`:
- ```
- The type Namespace.MotorCycle does not implement the 'service type' Namespace.ICar.
- ```
-
-- The given type is not resolvable like `Register()`:
- ```
- The type Namespace.IService could not be resolved.
- It's probably an interface, abstract class, or primitive type.
- ```
-
-## LifetimeValidationFailedException
-This exception pops up when the lifetime validation feature of the container is turned on and the validation fails at some point. Depending on which phase of the validation failed, the exception message could be one of the following:
-- When the life-span of a dependency is shorter than its parent's:
- ```
- The life-span of Namespace.Service (ScopedLifetime|10) is shorter than
- its direct or indirect parent's Namespace.Dependency (Singleton|20).
- This could lead to incidental lifetime promotions with longer life-span,
- it's recommended to double-check your lifetime configurations.
- ```
-
-- When a `Scoped` service is requested from the root scope:
- ```
- Resolution of Namespace.Service (ScopedLifetime) from the 'root scope' is not allowed,
- that would promote the service's lifetime to a singleton.
- ```
-
-## AggregateException
-This exception can occur only when the `.Validate()` method of the container is called. This function goes through the whole resolution tree and collects the issues into a collection. If it finds any, it puts the result into an `AggregateException` and throws it.
\ No newline at end of file
diff --git a/docs/diagnostics/utilities.md b/docs/diagnostics/utilities.md
index 8bbe9493..cf25f9d4 100644
--- a/docs/diagnostics/utilities.md
+++ b/docs/diagnostics/utilities.md
@@ -1,6 +1,7 @@
# Utilities
+These are additional utility functions of Stashbox that can be helpful in some cases.
-## Is registered?
+## Is Registered?
There might be cases where you want to find out whether a service is registered into the container or not.
@@ -23,7 +24,7 @@ bool isIJobRegistered = container.IsRegistered("DbBackup");
-## Can resolve?
+## Can Resolve?
There might be cases where rather than finding out that a service is registered, you are more interested in that it's resolvable from the container's actual state or not.
@@ -46,7 +47,7 @@ bool isIJobResolvable = container.CanResolve("DbBackup");
-## Get all mappings
+## Get All Mappings
You can get all registrations in a key-value pair collection (where the key is the service type and the value is the actual registration) by calling the `.GetRegistrationMappings()` method.
@@ -57,12 +58,12 @@ IEnumerable> mappings =
```
-## Registration diagnostics
+## Registration Diagnostics
You can get a much more readable version of the registration mappings by calling the `.GetRegistrationDiagnostics()` method.
-`RegistrationDiagnosticsInfo` has an overridden `.ToString()` method which returns the info formatted in a human-readable form.
+`RegistrationDiagnosticsInfo` has an overridden `.ToString()` method that returns the mapping details formatted in a human-readable form.
```cs
container.Register("DbBackupJob");
diff --git a/docs/diagnostics/validation.md b/docs/diagnostics/validation.md
index c9c9161b..7f0a382c 100644
--- a/docs/diagnostics/validation.md
+++ b/docs/diagnostics/validation.md
@@ -1,30 +1,84 @@
-# Container validation
+# Validation
-Stashbox has validation routines that help you detect and solve common misconfiguration issues. You can verify the container's actual state with its `.Validate()` method. This method walks through the whole resolution tree and collects all the issues into an [AggregateException](diagnostics/exceptions?id=aggregateexception).
+Stashbox has validation routines that help you detect and solve common misconfiguration issues. You can verify the container's actual state with its `.Validate()` method. This method walks through the whole resolution tree and collects all the issues into an `AggregateException`.
-## Registration validation
-The container executes validation against the given types during registration.
-The validation fails when:
-- The implementation type is not resolvable (it's an interface or an abstract class).
-- The implementation type does not implement the service type.
-- The given implementation type is already registered and the `RegistrationBehavior` [container configuration option](configuration/container-configuration?id=registration-behavior) is set to `ThrowException`.
+## Registration Validation
+The container validates the given types during registration and throws the following exceptions when the validation fails.
+### InvalidRegistrationException
+- **When the implementation type is not resolvable** (it's an interface or abstract class registered like: `Register()`):
+ ```
+ The type Namespace.IService could not be resolved. It's probably an interface, abstract class, or primitive type.
+ ```
+- **When the implementation type does not implement the service type**:
+ ```
+ The type Namespace.MotorCycle does not implement the 'service type' Namespace.ICar.
+ ```
-When any of the above occurs, the container throws a specific [exception](diagnostics/exceptions?id=invalidregistrationexception).
+### ServiceAlreadyRegisteredException
+- **When the given implementation type is already registered** and the `RegistrationBehavior` [container configuration option](configuration/container-configuration?id=registration-behavior) is set to `ThrowException`:
+ ```
+ The type Namespace.Service is already registered.
+ ```
-## Resolution validation
-During the construction of the resolution tree, the container constantly checks its actual state to ensure its stability.
+## Resolution Validation
+During the construction of the resolution tree, the container continuously checks its actual state to ensure stability. When any of the following issues occur, the container throws a `ResolutionFailedException`.
-### Unresolvable type
-When a requested type is not instantiable (e.g., it doesn't have a public constructor), the container throws an [exception](diagnostics/exceptions?id=resolutionfailedexception) with every diagnostic detail included.
+1. **When a dependency is missing from the resolution tree**:
-### Missing dependency
-When a type the requested service is depending on is not resolvable (either as a constructor parameter or a class member), the container throws an [exception](diagnostics/exceptions?id=resolutionfailedexception) with every diagnostic detail included, like which constructors were tried for resolution and which parameters were unable to resolve.
+
+ #### **Parameter**
+ ```cs
+ class Service
+ {
+ public Service(Dependency dep) { }
-### Lifetime validation
-This validation enforces the following rules, and when they are being violated, the container throws an [exception](diagnostics/exceptions?id=lifetimevalidationfailedexception).
- 1. **When a scoped service is requested from the root scope**. As the root scope's lifetime is bound to the container's lifetime, this action would unintentionally promote the scoped service's lifetime to a singleton.
-
- 2. **When the life-span of a dependency is shorter than its parent's**. It's called [captive dependency](https://blog.ploeh.dk/2014/06/02/captive-dependency/). Every lifetime has a `LifeSpan` value, which determines how long the related service lives. The main rule is that services may not contain dependencies with shorter life-spans like singletons should not depend on scoped services. The only exception is the life-span value `0`, which indicates that the related service is state-less and could be injected into any service.
+ public Service(Dependency2 dep2) { }
+ }
+
+ container.Register();
+ var service = container.Resolve();
+ ```
+ This will result in the following exception message:
+
+ ```
+ Could not resolve type Namespace.Service.
+ Constructor Void .ctor(Dependency) found with unresolvable parameter: (Namespace.Dependency)dep.
+ Constructor Void .ctor(Dependency2) found with unresolvable parameter: (Namespace.Dependency2)dep2.
+ ```
+ #### **Property/field**
+ ```cs
+ class Service
+ {
+ public Dependency Dep { get; set; }
+ }
+
+ container.Register(options => options.WithDependencyBinding(s => s.Dep));
+ var service = container.Resolve();
+ ```
+ This will show the following message:
+ ```
+ Could not resolve type Namespace.Service.
+ Unresolvable property: (Namespace.Dependency)Dep.
+ ```
+
+
+2. **When the requested type is unresolvable**:
+ When a requested type is not instantiable (e.g., it doesn't have a public constructor), the following exception message shows:
+
+ ```
+ Could not resolve type Namespace.Service.
+ Service is not registered or unresolvable type requested.
+ ```
+
+## Lifetime Validation
+This validation enforces the following rules, and when they are being violated, the container throws a `LifetimeValidationFailedException`.
+1. **When a scoped service is requested from the root scope**. As the root scope's lifetime is bound to the container's lifetime, this action would unintentionally promote the scoped service's lifetime to singleton:
+ ```
+ Resolution of Namespace.Service (ScopedLifetime) from the 'root scope' is not allowed,
+ that would promote the service's lifetime to a singleton.
+ ```
+
+2. **When the life-span of a dependency is shorter than its parent's**. It's called [captive dependency](https://blog.ploeh.dk/2014/06/02/captive-dependency/). Every lifetime has a `LifeSpan` value, which determines how long the related service lives. The main rule is that services may not contain dependencies with shorter life-spans like singletons should not depend on scoped services. The only exception is the life-span value `0`, which indicates that the related service is state-less and could be injected into any service.
These are the current `LifeSpan` values:
- **Singleton**: 20
- **Scoped**: 10
@@ -32,5 +86,60 @@ This validation enforces the following rules, and when they are being violated,
- **PerScopedRequest**: 0
- **Transient**: 0
-### Circular dependency
-When the container notices an infinite dependency loop in the resolution tree, it throws an [exception](diagnostics/exceptions?id=circulardependencyexception) with every diagnostic detail included.
\ No newline at end of file
+ The exception message would be:
+ ```
+ The life-span of Namespace.Service (ScopedLifetime|10) is shorter than
+ its direct or indirect parent's Namespace.Dependency (Singleton|20).
+ This could lead to incidental lifetime promotions with longer life-span,
+ it's recommended to double-check your lifetime configurations.
+ ```
+
+## Circular Dependency
+When the container notices an infinite dependency loop in the resolution tree, it throws a `CircularDependencyException` with every diagnostic detail included.
+
+```cs
+class Service1
+{
+ public Service1(Service2 service2) { }
+}
+
+class Service2
+{
+ public Service2(Service1 service1) { }
+}
+
+container.Register();
+container.Register();
+var service = container.Resolve();
+```
+The exception message is:
+```
+Circular dependency detected during the resolution of Namespace.Service1.
+```
+
+## Other Exceptions
+### CompositionRootNotFoundException
+This exception pops up when we try to compose an assembly, but it doesn't contain an `ICompositionRoot` implementation.
+```cs
+container.ComposeAssembly(typeof(Service).Assembly);
+```
+The exception message is:
+```
+No ICompositionRoot found in the given assembly: {your-assembly-name}
+```
+
+### ConstructorNotFoundException
+During the registration phase, when you are using the `WithConstructorByArgumentTypes()` or `WithConstructorByArguments()` options, you can accidentally point to a non-existing constructor and in that case, the container throws a `ConstructorNotFoundException`.
+
+```cs
+class Service
+{
+ public Service(Dependency dep) { }
+}
+
+container.Register(options => options.WithConstructorByArgumentTypes(typeof(string), typeof(int)));
+```
+The exception message is:
+```
+Constructor not found for Namespace.Service with the given argument types: System.String, System.Int32.
+```
\ No newline at end of file
diff --git a/docs/getting-started/overview.md b/docs/getting-started/overview.md
index 3db74702..8dcfcb7e 100644
--- a/docs/getting-started/overview.md
+++ b/docs/getting-started/overview.md
@@ -8,9 +8,9 @@ These are the latest stable and pre-release versions available:
Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (daily)
--- | --- | --- | ---
-[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg)](https://www.fuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/)
+[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg?v=3.6.0)](https://www.fuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/)
-## Core attributes
+## Core Attributes
- 🚀 Fast, thread-safe, and lock-free operations.
- ⚡️ Easy-to-use Fluent configuration API.
- ♻️ Small memory footprint.
@@ -18,7 +18,7 @@ Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (daily)
- 🚨 Detects and warns about misconfigurations.
- 🔥 Gives fast feedback on registration/resolution issues.
-## Supported platforms
+## Supported Platforms
- .NET 4.0 and above
- .NET Core
- Mono
diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md
index 6388b8d2..46ec8eb8 100644
--- a/docs/getting-started/quick-start.md
+++ b/docs/getting-started/quick-start.md
@@ -46,7 +46,7 @@ To achieve the most efficient usage of Stashbox, you should follow these steps:
!> Don't create new `StashboxContainer` instances continuously. Such action will bypass the container's internal delegate cache and could lead to poor performance.
-## How it works?
+## How It Works?
Stashbox builds and maintains a collection from the registered services. At first, during the service resolution, Stashbox looks for a service registration with a matching service type. Then it scans the registration's implementation type for all available constructors and selects the one with the most arguments it knows how to resolve by looking for matching service registrations again.
When every constructor argument (and field/property if applicable) has a matching registration, Stashbox jumps to the first argument and does the same scanning on its type.
@@ -104,7 +104,7 @@ public class DbBackup : IJob
}
```
-?> By only depending on interfaces, you decouple your services from concrete implementations. This gives you the advantage and flexibility of more comfortable implementation replacement and isolates your components from each other. For example, these are helping a lot at unit testing with the possibility of injecting mock implementations.
+?> By depending only on interfaces, you decouple your services from concrete implementations. This gives you the flexibility of a more comfortable implementation replacement and isolates your components from each other. For example, unit testing benefits a lot from the possibility of replacing a real implementation with mock objects.
The example services above used with Stashbox in a Console Application:
diff --git a/docs/index.html b/docs/index.html
index ce804e52..f1411d83 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -35,7 +35,7 @@
},
copyCode: {
- buttonText : '',
+ buttonText : '',
},
search: {
diff --git a/docs/usage/advanced-registration.md b/docs/usage/advanced-registration.md
index 384f6626..b93bf6c0 100644
--- a/docs/usage/advanced-registration.md
+++ b/docs/usage/advanced-registration.md
@@ -1,4 +1,4 @@
-# Advanced registration
+# Advanced Registration
This section is about Stashbox's further configuration options, including the registration configuration API, the registration of factory delegates, multiple implementations, batch registration, the concept of the [Composition Root](https://blog.ploeh.dk/2011/07/28/CompositionRoot/) and many more.
?> This section won't cover all the available options of the registrations API, but you can find them [here](configuration/registration-configuration).
@@ -6,49 +6,71 @@ This section is about Stashbox's further configuration options, including the re
-## Factory registration
+## Factory Registration
-You have the option to bind a factory delegate to a registration that will be invoked by the container directly to instantiate your service.
+You have the option to bind a factory delegate to a registration that the container will invoke directly to instantiate your service.
-You can choose between a delegate that gets the current dependency resolver as an argument (used to resolve further services inside the factory) and a parameterless delegate.
+You can use parameter-less and custom parameterized delegates as a factory.
+
+You can also get the current dependency resolver as a delegate parameter used to resolve any additional dependencies required for the service construction.
-#### **With resolver parameter**
+#### **Parameter-less**
+```cs
+container.Register(options => options
+ .WithFactory(() => new ConsoleLogger());
+
+// the container uses the factory for instantiation.
+IJob job = container.Resolve();
+```
+
+#### **Parameterized**
```cs
container.Register(options => options
- .WithFactory(resolver => new DbBackup(resolver.Resolve()));
-// The container uses the factory for instantiation.
+ .WithFactory(logger => new DbBackup(logger));
+
+// the container uses the factory for instantiation.
IJob job = container.Resolve();
```
-#### **Parameter-less**
+#### **Resolver parameter**
```cs
-container.Register(options => options
- .WithFactory(() => new ConsoleLogger());
-// The container uses the factory for instantiation.
-IJob job = container.Resolve();
+container.Register(options => options
+ .WithFactory(resolver => new DbBackup(resolver.Resolve()));
+
+// the container uses the factory for instantiation.
+IJob job = container.Resolve();
```
+
+
+
+
+
Delegate factories are useful when your service's instantiation is not straight-forward for the container, like when it depends on something that is not available at resolution time. E.g., a connection string.
```cs
container.Register(options => options
- .WithFactory(resolver =>
- new DbBackup(Configuration["ConnectionString"],
- resolver.Resolve()));
+ .WithFactory(logger =>
+ new DbBackup(Configuration["DbConnectionString"], logger));
```
+
+
+
+
-### Factory with custom parameters
+### Factory with Parameter Override
Suppose you'd want to use custom parameters for your service's instantiation rather than captured variables in lambda closures. In that case, you can register a `Func<>` delegate that you can use with parameters at resolution time.
+?> This example is about pre-registered factories; however, the container can also implicitly [wrap](advanced/generics?id=func) your service in a `Func<>` without pre-registering.
@@ -70,7 +92,77 @@ Delegate backupFactory = container.ResolveFactory(typeof(IJob),
parameterTypes: new[] { typeof(string) });
IJob dbBackup = backupFactory.DynamicInvoke(Configuration["ConnectionString"]);
```
+
+
+
+
+
+
+
+### Consider These Before Using the Resolver Parameter Inside a Factory
+Delegate factories are a black-box for the container. It doesn't have much control over what's happening inside them, which means when you resolve additional dependencies with the dependency resolver parameter, they could easily bypass the [lifetime](diagnostics/validation?id=lifetime-validation) and [circular dependency](diagnostics/validation?id=circular-dependency) validations. Fortunately, there are options to keep them validated anyway:
+
+- **Parameterized factories instead of resolver**: rather than using the dependency resolver parameter inside the factory, let the container inject the dependencies into the delegate as parameters. With this, the resolution tree's integrity remains stable because no service resolution happens inside the black-box, and each parameter is validated.
+
+- There is a [container configuration option](configuration/container-configuration?id=circular-dependencies-in-delegates) that enables **circular dependency tracking even across delegates that uses the resolver parameter** to resolve dependencies. When this option is enabled, the container generates extra expression nodes into the resolution tree to detect circles, but at the price of a much more complex tree structure and longer dependency walkthrough.
+
+
+
+
+#### **Parameterized factory**
+```cs
+interface IEventProcessor { }
+
+class EventProcessor : IEventProcessor
+{
+ public EventProcessor(ILogger logger, IEventValidator validator)
+ { }
+}
+
+container.Register();
+container.Register();
+
+container.Register(options => options
+ // Ilogger and IEventValidator instances are injected
+ // by the container at resolution time, so they will be
+ // validated against circular and captive dependencies.
+ .WithFactory((logger, validator) =>
+ new EventProcessor(logger, validator));
+
+// the container resolves ILogger and IEventValidator first, then
+// it passes them to the factory as delegate parameters.
+IEventProcessor processor = container.Resolve();
+```
+
+#### **Resolver with circle tracking**
+```cs
+interface IEventProcessor { }
+
+class EventProcessor : IEventProcessor
+{
+ public EventProcessor(ILogger logger, IEventValidator validator)
+ { }
+}
+
+// enabling the circular dependency tracking across factory delegates.
+using var container = new StashboxContainer(options =>
+ options.WithRuntimeCircularDependencyTracking());
+container.Register();
+container.Register();
+
+container.Register(options => options
+ // Ilogger and IEventValidator instances are resolved by the
+ // passed resolver, so they will bypass the circular and captive
+ // dependency validation. However the extra tracker nodes will catch
+ // the circular dependencies anyway.
+ .WithFactory(resolver => new EventProcessor(
+ resolver.Resolve(), resolver.Resolve()));
+
+// the container uses the factory to instantiate the processor, and
+// generates the extra circle tracker expression nodes into the tree.
+IEventProcessor processor = container.Resolve();
+```
@@ -78,7 +170,7 @@ IJob dbBackup = backupFactory.DynamicInvoke(Configuration["ConnectionString"]);
-## Multiple implementations
+## Multiple Implementations
As we previously saw in the [Named registration](usage/basics?id=named-registration) topic, Stashbox allows you to have multiple implementations bound to a particular service type. You can use names to distinguish them, but you can also access them by requesting a typed collection using the service type.
@@ -151,12 +243,20 @@ IJob job = container.Resolve();
-## Map to multiple services
+## Map to Multiple Services
-When you have an implementation that implements multiple interfaces, you have the option to bind its registration to all or some of those interfaces/base types.
+When you have a service that implements multiple interfaces, you have the option to bind its registration to all or some of those additional interfaces or base types.
+
+Suppose we have the following class declaration:
+```cs
+class DbBackup : IJob, IScheduledJob
+{
+ public DbBackup() { }
+}
+```
+
-In these examples, we assume that `DbBackup` implements `IScheduledJob` alongside `IJob`.
@@ -187,7 +287,7 @@ DbBackup job = container.Resolve(); // DbBackup
-## Batch registration
+## Batch Registration
You have the option to register multiple services in a single registration operation.
@@ -321,7 +421,7 @@ DbBackup backup = container.Resolve(); // error, not found
-## Assembly registration
+## Assembly Registration
@@ -329,7 +429,7 @@ The batch registration API's signature *(filters, registration configuration act
In this example, we assume that the same three services used at the batch registration are in the same assembly.
-?> The container also detects and registers open-generic definitions (when applicable) from the supplied type collection. You can read about [open-generics here](advanced/generics?id=open-generic).
+?> The container also detects and registers open-generic definitions (when applicable) from the supplied type collection. You can read about [open-generics here](advanced/generics?id=open-generics).
@@ -387,16 +487,16 @@ DbBackup backup = container.Resolve(); // error, not found
-## Composition root
+## Composition Root
-The entry point of components, where all the services are wired together, is often referred to as [Composition Root](https://blog.ploeh.dk/2011/07/28/CompositionRoot/).
+The [Composition Root](https://blog.ploeh.dk/2011/07/28/CompositionRoot/) is an entry point, where all services required to make a component functional are wired together.
-Stashbox provides an `ICompositionRoot` interface used to detect user-defined entry points in given components or assemblies.
+Stashbox provides an `ICompositionRoot` interface that can be used to define an entry point for a given component or even for an entire assembly.
-You can wire up your *composition root* implementation with `ComposeBy()`, or you can let the container find and execute all available implementations within an assembly.
+You can wire up your *composition root* implementation with `ComposeBy()`, or you can let the container find and execute all available *composition roots* within an assembly.
-?> Your `ICompositionRoot` implementations also can have their dependencies that would be resolved by the container.
+?> Your `ICompositionRoot` implementation also can have dependencies that the container will inject.
@@ -429,7 +529,7 @@ container.ComposeAssembly(typeof(IServiceA).Assembly);
#### **Override**
```cs
-// compose a single root with overridden dependency.
+// compose a single root with dependency override.
container.ComposeBy(new CustomRootDependency());
```
@@ -440,7 +540,7 @@ container.ComposeBy(new CustomRootDependency());
-## Injection parameters
+## Injection Parameters
If you have some pre-evaluated dependencies you'd like to inject at resolution time, you can set them as an injection parameter during registration.
diff --git a/docs/usage/basics.md b/docs/usage/basics.md
index 48b9cb79..ca1263ad 100644
--- a/docs/usage/basics.md
+++ b/docs/usage/basics.md
@@ -1,18 +1,18 @@
-# Basic usage
+# Basic Usage
This section is about the basics of Stashbox's API. It will give you a good starting point for more advanced topics described in the following sections.
Stashbox provides several methods that enable registering services, and we'll go through the most common scenarios with code examples.
-## Default registration
+## Default Registration
Stashbox allows registration operations via `Register()` methods.
-During registration, the container checks whether the service type is assignable from the implementation type and if not, the container throws an [exception](diagnostics/exceptions?id=invalidregistrationexception).
+During registration, the container checks whether the service type is assignable from the implementation type and if not, the container throws an [exception](diagnostics/validation?id=registration-validation).
-Also, when the implementation is not resolvable (when it's an interface or abstract class), the container throws the same [exception](diagnostics/exceptions?id=invalidregistrationexception).
+Also, when the implementation is not resolvable (when it's an interface or abstract class), the container throws the same [exception](diagnostics/validation?id=registration-validation).
The example registers `DbBackup` to be returned when `IJob` is requested.
@@ -82,7 +82,7 @@ var job = container.Register()
-## Named registration
+## Named Registration
The example shows how you can bind more implementations to a service type using names for identification.
@@ -113,7 +113,7 @@ object cleanup = container.Resolve(typeof(IJob), "StorageCleanup");
-## Instance registration
+## Instance Registration
With instance registration, you can provide an already created external instance to use when the given service type is requested.
@@ -220,7 +220,7 @@ jobs = container.ResolveAll(typeof(IJob));
-## Wiring up
+## Wiring Up
Wiring up is similar to the [Instance registration](#instance-registration) except that the container will perform property/field injection (if configured so and applicable) on the registered instance during resolution.
@@ -245,7 +245,7 @@ object job = container.Resolve(typeof(IJob));
-## Lifetime shortcuts
+## Lifetime Shortcuts
The service's lifetime indicates how long the service's instance will live and the re-using policy applied when it gets injected.
diff --git a/docs/usage/lifetimes.md b/docs/usage/lifetimes.md
index c6f13455..bd0ba6b7 100644
--- a/docs/usage/lifetimes.md
+++ b/docs/usage/lifetimes.md
@@ -8,7 +8,7 @@ Lifetime management is the concept of controlling how long a service's instances
-## Default lifetime
+## Default Lifetime
When you are not specifying a lifetime during registration, Stashbox will use the default lifetime. By default, it's set to **Transient**, but you can override it with the `.WithDefaultLifetime()` [container configuration option](configuration/container-configuration?id=default-lifetime).
@@ -41,7 +41,7 @@ var container = new StashboxContainer(options => options
-## Transient lifetime
+## Transient Lifetime
A new instance will be created for every resolution request. If a transient is referred by multiple consumers in the same resolution tree, each of them will get its instance.
@@ -61,7 +61,7 @@ container.Register(options => options
-## Singleton lifetime
+## Singleton Lifetime
A single instance will be created and reused for every resolution request and injected into every consumer.
@@ -90,7 +90,7 @@ container.RegisterSingleton();
-## Scoped lifetime
+## Scoped Lifetime
A new instance is created for each [scope](usage/scopes), and that instance will be returned (and reused) for every resolution request initiated on the given scope. It's like the singleton lifetime within a scope.
@@ -125,7 +125,7 @@ IJob job = scope.Resolve();
-## Named scope lifetime
+## Named Scope Lifetime
It is the same as the scoped lifetime, except that the given service will be selected only when the resolution request is initiated on a scope with the same name.
@@ -183,7 +183,7 @@ DbJobExecutor executor = scope.Resolve();
-## Per scoped request lifetime
+## Per Scoped Request Lifetime
The requested service will behave like a singleton with this lifetime, but only within a scoped dependency request. That means every scoped service will get a new exclusive instance that will be used by its sub-dependencies as well.
@@ -197,7 +197,7 @@ container.Register(options => options
-## Custom lifetime
+## Custom Lifetime
Suppose you'd like to use a custom lifetime. In that case, you can create your implementation by inheriting either from `FactoryLifetimeDescriptor` or from `ExpressionLifetimeDescriptor`, depending on how do you want to manage the given service instances. Then you can pass it to the `WithLifetime()` configuration method.
- **ExpressionLifetimeDescriptor**: With this, you can build your lifetime with the expression form of the service instantiation.
diff --git a/docs/usage/scopes.md b/docs/usage/scopes.md
index 5ca6f583..b449546c 100644
--- a/docs/usage/scopes.md
+++ b/docs/usage/scopes.md
@@ -5,7 +5,7 @@ It's an implementation of the unit-of-work pattern; a scope encapsulates a given
A web application is a fair usage example for scopes as it has a well-defined execution unit that can be bound to a scope - the HTTP request. Every request could have its unique scope attached to the request's lifetime. When a request ends, the scope gets closed, and all the scoped instances will be disposed.
-## Creating a scope
+## Creating a Scope
You can create a scope from the container by calling its `.BeginScope()` method.
@@ -67,7 +67,7 @@ scope.Dispose();
-## Named scopes
+## Named Scopes
@@ -133,7 +133,7 @@ using (var unNamed = container.BeginScope())
```
-## Service as scope
+## Service As Scope
@@ -192,7 +192,7 @@ scope.Dispose();
-## Add instance to a scope
+## Add Instance to a Scope
diff --git a/docs/usage/service-resolution.md b/docs/usage/service-resolution.md
index 8077e95b..bfdf7455 100644
--- a/docs/usage/service-resolution.md
+++ b/docs/usage/service-resolution.md
@@ -1,14 +1,14 @@
-# Service resolution
+# Service Resolution
When you have all your components registered and configured adequately, you can resolve them from the container or a [scope](usage/scopes) by requesting their service type.
Resolving from the container means that the requested service (if it's scoped or singleton) will be resolved from the container's root scope.
-During a service's resolution, the container automatically walks through the entire dependency hierarchy and resolves all of those which are required for construction.
+During a service's resolution, the container automatically walks through the entire dependency hierarchy and resolves all of those required for construction.
When it encounters any violations of [these validation rules](diagnostics/validation?id=resolution-validation) *(circular dependencies, missing required services, lifetime misconfigurations)* during the walkthrough, it lets you know that something is wrong by throwing the appropriate [exception](diagnostics/exceptions).
-## Injection patterns
+## Injection Patterns
**Constructor injection** is the *primary dependency injection pattern*. It encourages the organization of the dependencies to a single place - the constructor.
@@ -17,7 +17,7 @@ Stashbox, by default, uses the constructor that has the most parameters it knows
*[Property/field injection](configuration/registration-configuration?id=propertyfield-injection)* is also supported in cases where constructor injection is not applicable.
-!> It's a common mistake to use the *property/field injection* only to disencumber the constructor from having too many parameters. That's a code smell and also a violation of the [Single-responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle). If you recognize these conditions, you might consider not adding that extra property injected dependency into your class but instead split it into multiple smaller units.
+!> It's a common mistake to use the *property/field injection* only to disencumber the constructor from having too many parameters. That's a code smell and also a violation of the [Single-responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle). If you recognize these conditions, you might consider not adding that extra property-injected dependency into your class but instead split it into multiple smaller units.
?> [Constructor selection](configuration/container-configuration?id=constructor-selection) and [property/field injection](configuration/container-configuration?id=auto-member-injection) is also configurable container-wide.
@@ -86,7 +86,7 @@ Attributes give you control over how Stashbox selects dependencies during a serv
**InjectionMethod attribute**: marks a method to be called when the requested service is being instantiated.
-!> Attributes provide a more straightforward configuration, but using them also tightens the bond between your application and Stashbox. If that's an issue for you, the same functionality is available on the *registration API* as **dependency binding**.
+!> Attributes provide a more straightforward configuration, but using them also tightens the bond between your application and Stashbox. If that's an issue for you, the same functionality is available on the *registration API* as [dependency binding](usage/service-resolution?id=dependency-binding).
@@ -158,14 +158,14 @@ IJob job = container.Resolve();
-## Dependency binding
+## Dependency Binding
The same dependency configuration as attributes have is available using the registration configuration API.
-**Bind to parameter**: it has the same functionality as the `Dependency` attribute on a constructor or method parameter.
+**Bind to parameter**: it has the same functionality as the [Dependency attribute](usage/service-resolution?id=attributes) on a constructor or method parameter, enables the named resolution.
-**Bind to property/field**: it has the same functionality as the `Dependency` attribute on a property or field.
+**Bind to property/field**: it has the same functionality as the [Dependency attribute](usage/service-resolution?id=attributes) on a property or field, enables the injection of the given member.
?> There are more overloads [available](configuration/registration-configuration?id=dependency-configuration).
@@ -203,7 +203,7 @@ container.Register("File");
// registration of service with the member injection.
container.Register(options => options
- .InjectMember("Logger", "Console"));
+ .WithDependencyBinding("Logger", "Console"));
// the container will resolve DbBackup with ConsoleLogger.
IJob job = container.Resolve();
@@ -215,7 +215,7 @@ IJob job = container.Resolve();
-## Conventional resolution
+## Conventional Resolution
When you enable the conventional resolution, the container treats the member and method parameter names as their dependency identifier.
@@ -228,7 +228,7 @@ new StashboxContainer(options => options
.TreatParameterAndMemberNameAsDependencyName());
```
-?> The container will attempt to apply named resolution on every dependency based on their parameter/property/field name.
+?> The container will attempt named resolution on every dependency based on parameter or property/field name.
@@ -279,7 +279,7 @@ IJob job = container.Resolve();
-## Conditional resolution
+## Conditional Resolution
Stashbox can resolve a particular dependency based on its context. This context is typically the reflected type information of the dependency, its usage, and the type it gets injected into.
@@ -290,7 +290,9 @@ Stashbox can resolve a particular dependency based on its context. This context
**Custom**: with this, you can build your selection logic based on the passed contextual type information.
-The same filters are applied to the result set when a **collection** is requested.
+The specified conditions are behaving like filters when a **collection** is requested.
+
+When you use the same conditional option multiple times, the container will evaluate them **combined with OR** logical operator.
@@ -301,7 +303,7 @@ class ConsoleAttribute : Attribute { }
class DbBackup : IJob
{
- public DbBackup([Console]ILogger consoleLogger)
+ public DbBackup([Console]ILogger logger)
{ }
}
@@ -320,7 +322,7 @@ IJob job = container.Resolve();
```cs
class DbBackup : IJob
{
- public DbBackup(ILogger consoleLogger)
+ public DbBackup(ILogger logger)
{ }
}
@@ -339,7 +341,7 @@ IJob job = container.Resolve();
```cs
class DbBackup : IJob
{
- public DbBackup(ILogger consoleLogger)
+ public DbBackup(ILogger logger)
{ }
}
@@ -354,25 +356,6 @@ container.Register();
IJob job = container.Resolve();
```
-#### **Name**
-```cs
-class DbBackup : IJob
-{
- public DbBackup(ILogger consoleLogger)
- { }
-}
-
-container.Register(options => options
- // inject only when we are currently resolving
- // in a service with the name 'dbBackup'.
- .When(typeInfo => typeInfo.DependencyName.Equals("dbBackup")));
-
-container.Register("dbBackup");
-
-// the container will resolve DbBackup with ConsoleLogger.
-IJob job = container.Resolve("dbBackup");
-```
-
#### **Collection**
```cs
class DbJobsExecutor : IJobsExecutor
@@ -389,9 +372,37 @@ ontainer.Register();
container.Register();
-// jobsExecutor will get an enumerable with DbBackup and DbCleanup in it.
+// jobsExecutor will get DbBackup and DbCleanup within a collection.
IJobsExecutor jobsExecutor = container.Resolve();
```
+
+#### **Combine**
+```cs
+class DbBackup : IJob
+{
+ public DbBackup(ILogger logger)
+ { }
+}
+
+class StorageCleanup : IJob
+{
+ public DbBackup(ILogger logger)
+ { }
+}
+
+container.Register(options => options
+ // inject only when we are
+ // currently resolving DbBackup OR StorageCleanup.
+ .WhenDependantIs()
+ .WhenDependantIs());
+
+container.Register();
+container.Register();
+
+// the collection will contain DbBackup and StorageCleanup
+// constructed with ConsoleLogger.
+IEnumerable jobs = container.ResolveAll();
+```
@@ -399,7 +410,7 @@ IJobsExecutor jobsExecutor = container.Resolve();
-## Optional resolution
+## Optional Resolution
In cases where it's not guaranteed that a service is resolvable, either because it's not registered or any of its dependencies are missing, you can attempt an optional resolution by using the `nullResultAllowed` parameter of the `.Resolve()` method.
@@ -431,7 +442,7 @@ object job = container.Resolve(typeof(IJob));
-## Dependency override
+## Dependency Override
@@ -504,7 +515,7 @@ object backup = container.Activate(typeof(DbBackup), new ConsoleLogger());
-### Building up
+### Building Up
You can also do the same *on the fly* activation post-processing (member/method injection) on already constructed instances with the `.BuildUp()` method.
diff --git a/src/ContainerContext.cs b/src/ContainerContext.cs
index ddb6d8c2..85eb68e2 100644
--- a/src/ContainerContext.cs
+++ b/src/ContainerContext.cs
@@ -14,7 +14,8 @@ public ContainerContext(IContainerContext parentContext, ResolutionStrategy reso
this.ParentContext = parentContext;
this.RegistrationRepository = new RegistrationRepository(containerConfiguration);
this.DecoratorRepository = new DecoratorRepository();
- this.RootScope = new ResolutionScope(resolutionStrategy, expressionFactory, this);
+ this.RootScope = new ResolutionScope(expressionFactory, this);
+ this.ResolutionStrategy = resolutionStrategy;
}
public IRegistrationRepository RegistrationRepository { get; }
@@ -25,6 +26,9 @@ public ContainerContext(IContainerContext parentContext, ResolutionStrategy reso
public IResolutionScope RootScope { get; }
+ public IResolutionStrategy ResolutionStrategy { get; }
+
public ContainerConfiguration ContainerConfiguration { get; internal set; }
+
}
}
diff --git a/src/Expressions/ExpressionBuilder.Factory.cs b/src/Expressions/ExpressionBuilder.Factory.cs
index dbb0221d..7e75db70 100644
--- a/src/Expressions/ExpressionBuilder.Factory.cs
+++ b/src/Expressions/ExpressionBuilder.Factory.cs
@@ -1,6 +1,8 @@
-using Stashbox.Registration;
+using Stashbox.Exceptions;
+using Stashbox.Registration;
using Stashbox.Resolution;
using System;
+using System.Collections.Generic;
using System.Linq.Expressions;
namespace Stashbox.Expressions
@@ -9,23 +11,38 @@ internal partial class ExpressionBuilder
{
private Expression GetExpressionForFactory(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType)
{
- var expression = serviceRegistration.RegistrationContext.ContainerFactory != null
- ? ConstructFactoryExpression(serviceRegistration.RegistrationContext.ContainerFactory,
- serviceRegistration, resolutionContext.CurrentScopeParameter)
- : ConstructFactoryExpression(serviceRegistration.RegistrationContext.SingleFactory, serviceRegistration);
+ if (resolutionContext.WeAreInCircle(serviceRegistration.RegistrationId))
+ throw new CircularDependencyException(serviceRegistration.ImplementationType);
- return this.expressionFactory.ConstructBuildUpExpression(serviceRegistration, resolutionContext, expression, resolveType);
+ resolutionContext.PullOutCircularDependencyBarrier(serviceRegistration.RegistrationId);
+
+ var parameters = GetFactoryParameters(serviceRegistration, resolutionContext, resolveType);
+ var expression = ConstructFactoryExpression(serviceRegistration, parameters);
+ var result = this.expressionFactory.ConstructBuildUpExpression(serviceRegistration, resolutionContext, expression, resolveType);
+
+ resolutionContext.LetDownCircularDependencyBarrier();
+ return result;
}
- private static Expression ConstructFactoryExpression(Delegate @delegate, ServiceRegistration serviceRegistration, params Expression[] parameters)
+ private Expression ConstructFactoryExpression(ServiceRegistration serviceRegistration, IEnumerable parameters)
{
- if (serviceRegistration.RegistrationContext.IsFactoryDelegateACompiledLambda || @delegate.IsCompiledLambda())
- return @delegate.InvokeDelegate(parameters);
+ if (serviceRegistration.RegistrationContext.IsFactoryDelegateACompiledLambda || serviceRegistration.RegistrationContext.Factory.IsCompiledLambda())
+ return serviceRegistration.RegistrationContext.Factory.InvokeDelegate(parameters);
- var method = @delegate.GetMethod();
+ var method = serviceRegistration.RegistrationContext.Factory.GetMethod();
return method.IsStatic
? method.CallStaticMethod(parameters)
- : method.CallMethod(@delegate.Target.AsConstant(), parameters);
+ : method.CallMethod(serviceRegistration.RegistrationContext.Factory.Target.AsConstant(), parameters);
+ }
+
+ private IEnumerable GetFactoryParameters(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType)
+ {
+ var length = serviceRegistration.RegistrationContext.FactoryParameters.Length;
+ for (int i = 0; i < length - 1; i++)
+ {
+ var typeInfo = new TypeInformation(serviceRegistration.RegistrationContext.FactoryParameters[i], null);
+ yield return resolutionContext.CurrentContainerContext.ResolutionStrategy.BuildExpressionForType(resolutionContext, typeInfo);
+ }
}
}
}
diff --git a/src/Expressions/ExpressionBuilder.cs b/src/Expressions/ExpressionBuilder.cs
index ce92bed0..cd709594 100644
--- a/src/Expressions/ExpressionBuilder.cs
+++ b/src/Expressions/ExpressionBuilder.cs
@@ -1,4 +1,5 @@
-using Stashbox.Lifetime;
+using Stashbox.Exceptions;
+using Stashbox.Lifetime;
using Stashbox.Registration;
using Stashbox.Resolution;
using Stashbox.Utils;
@@ -42,8 +43,7 @@ private Expression BuildExpressionByRegistrationType(ServiceRegistration service
return serviceRegistration.RegistrationType switch
{
- RegistrationType.Factory => this.GetExpressionForFactory(serviceRegistration, resolutionContext,
- requestedType),
+ RegistrationType.Factory => this.GetExpressionForFactory(serviceRegistration, resolutionContext, requestedType),
RegistrationType.Instance => serviceRegistration.RegistrationContext.ExistingInstance.AsConstant(),
RegistrationType.WireUp => this.expressionFactory.ConstructBuildUpExpression(serviceRegistration,
resolutionContext, serviceRegistration.RegistrationContext.ExistingInstance.AsConstant(),
diff --git a/src/Expressions/ExpressionFactory.Member.cs b/src/Expressions/ExpressionFactory.Member.cs
index bea72304..1eadf3f9 100644
--- a/src/Expressions/ExpressionFactory.Member.cs
+++ b/src/Expressions/ExpressionFactory.Member.cs
@@ -43,7 +43,8 @@ private static Expression GetMemberExpression(
var injectionParameter = registrationContext.InjectionParameters.SelectInjectionParameterOrDefault(memberTypeInfo);
if (injectionParameter != null) return injectionParameter;
- var memberExpression = resolutionContext.ResolutionStrategy.BuildExpressionForType(resolutionContext, memberTypeInfo);
+ var memberExpression = resolutionContext.CurrentContainerContext
+ .ResolutionStrategy.BuildExpressionForType(resolutionContext, memberTypeInfo);
if (memberExpression != null || resolutionContext.NullResultAllowed) return memberExpression;
diff --git a/src/Expressions/ExpressionFactory.Method.cs b/src/Expressions/ExpressionFactory.Method.cs
index 28d4defb..f9352491 100644
--- a/src/Expressions/ExpressionFactory.Method.cs
+++ b/src/Expressions/ExpressionFactory.Method.cs
@@ -27,7 +27,7 @@ private IEnumerable CreateParameterExpressionsForMethod(
var injectionParameter = registrationContext.InjectionParameters.SelectInjectionParameterOrDefault(parameter);
if (injectionParameter != null) yield return injectionParameter;
- yield return resolutionContext.ResolutionStrategy.BuildExpressionForType(
+ yield return resolutionContext.CurrentContainerContext.ResolutionStrategy.BuildExpressionForType(
resolutionContext, parameter) ?? throw new ResolutionFailedException(method.DeclaringType, registrationContext.Name,
$"Method {method} found with unresolvable parameter: ({parameter.Type}){parameter.ParameterOrMemberName}");
}
@@ -118,7 +118,7 @@ private static bool TryBuildMethod(
var injectionParameter = registrationContext.InjectionParameters.SelectInjectionParameterOrDefault(parameter);
- parameterExpressions[i] = injectionParameter ?? resolutionContext.ResolutionStrategy
+ parameterExpressions[i] = injectionParameter ?? resolutionContext.CurrentContainerContext.ResolutionStrategy
.BuildExpressionForType(resolutionContext, parameter);
if (parameterExpressions[i] != null) continue;
diff --git a/src/Extensions/ExpressionExtensions.cs b/src/Extensions/ExpressionExtensions.cs
index 2f562e0b..2f4387a0 100644
--- a/src/Extensions/ExpressionExtensions.cs
+++ b/src/Extensions/ExpressionExtensions.cs
@@ -245,6 +245,15 @@ public static Expression AsLambda(this Expression expressi
public static MethodCallExpression CallStaticMethod(this MethodInfo methodInfo, params Expression[] parameters) =>
Expression.Call(methodInfo, parameters);
+ ///
+ /// Constructs a static method call expression from a and its parameters, => Expression.Call(methodInfo, parameters)
+ ///
+ /// The static method info.
+ /// The parameters.
+ /// The call expression.
+ public static MethodCallExpression CallStaticMethod(this MethodInfo methodInfo, IEnumerable parameters) =>
+ Expression.Call(methodInfo, parameters);
+
///
/// Constructs a method call expression from a target expression, method info and parameters, => Expression.Call(target, methodInfo, parameters)
///
@@ -275,6 +284,16 @@ public static MethodCallExpression CallMethod(this Expression target, MethodInfo
public static MethodCallExpression CallMethod(this MethodInfo methodInfo, Expression target, params Expression[] parameters) =>
target.CallMethod(methodInfo, parameters);
+ ///
+ /// Constructs a method call expression from a target expression, method info and parameters, => Expression.Call(target, methodInfo, parameters)
+ ///
+ /// The target expression.
+ /// The method info.
+ /// The parameters.
+ /// The call expression.
+ public static MethodCallExpression CallMethod(this MethodInfo methodInfo, Expression target, IEnumerable parameters) =>
+ target.CallMethod(methodInfo, parameters);
+
///
/// Constructs a convert expression, => Expression.Convert(expression, type)
///
@@ -301,6 +320,15 @@ public static InvocationExpression InvokeLambda(this LambdaExpression expression
public static InvocationExpression InvokeDelegate(this Delegate @delegate, params Expression[] parameters) =>
Expression.Invoke(@delegate.AsConstant(), parameters);
+ ///
+ /// Constructs an invocation expression, => Expression.Invoke(delegate.AsConstant(), parameters)
+ ///
+ /// The delegate to invoke.
+ /// The delegate parameters.
+ /// The invocation expression.
+ public static InvocationExpression InvokeDelegate(this Delegate @delegate, IEnumerable parameters) =>
+ Expression.Invoke(@delegate.AsConstant(), parameters);
+
///
/// Constructs an new expression, => Expression.New(constructor, arguments)
///
diff --git a/src/Extensions/TypeExtensions.cs b/src/Extensions/TypeExtensions.cs
index 07b895bb..68d1e6af 100644
--- a/src/Extensions/TypeExtensions.cs
+++ b/src/Extensions/TypeExtensions.cs
@@ -264,9 +264,9 @@ public static TypeInformation AsTypeInformation(this MemberInfo member,
var customAttributes = member.GetCustomAttributes();
var dependencyName = member.GetDependencyAttribute()?.Name;
- if (registrationContext.InjectionMemberNames.Count != 0 || containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled)
+ if (registrationContext.DependencyBindings.Count != 0 || containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled)
{
- if (registrationContext.InjectionMemberNames.TryGetValue(member.Name, out var foundNamedDependencyName))
+ if (registrationContext.DependencyBindings.TryGetValue(member.Name, out var foundNamedDependencyName))
dependencyName = foundNamedDependencyName;
else if (dependencyName == null && containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled)
dependencyName = member.Name;
@@ -363,7 +363,7 @@ private static bool FilterProperty(this PropertyInfo prop, RegistrationContext c
var valid = prop.CanWrite && !prop.IsIndexer() &&
(prop.GetDependencyAttribute() != null ||
publicPropsEnabled && prop.HasPublicSetMethod() || limitedPropsEnabled ||
- contextData.InjectionMemberNames.ContainsKey(prop.Name));
+ contextData.DependencyBindings.ContainsKey(prop.Name));
valid = valid && (containerConfiguration.AutoMemberInjectionFilter == null || containerConfiguration.AutoMemberInjectionFilter(prop));
valid = valid && (contextData.AutoMemberInjectionFilter == null || contextData.AutoMemberInjectionFilter(prop));
@@ -377,7 +377,7 @@ private static bool FilterField(this FieldInfo field, RegistrationContext contex
var valid = !field.IsInitOnly && !field.IsBackingField() &&
(field.GetDependencyAttribute() != null ||
fieldsEnabled ||
- contextData.InjectionMemberNames.ContainsKey(field.Name));
+ contextData.DependencyBindings.ContainsKey(field.Name));
valid = valid && (containerConfiguration.AutoMemberInjectionFilter == null || containerConfiguration.AutoMemberInjectionFilter(field));
valid = valid && (contextData.AutoMemberInjectionFilter == null || contextData.AutoMemberInjectionFilter(field));
diff --git a/src/IContainerContext.cs b/src/IContainerContext.cs
index 177f7393..57ecc888 100644
--- a/src/IContainerContext.cs
+++ b/src/IContainerContext.cs
@@ -1,5 +1,6 @@
using Stashbox.Configuration;
using Stashbox.Registration;
+using Stashbox.Resolution;
namespace Stashbox
{
@@ -23,6 +24,11 @@ public interface IContainerContext
///
IContainerContext ParentContext { get; }
+ ///
+ /// The resolution strategy.
+ ///
+ IResolutionStrategy ResolutionStrategy { get; }
+
///
/// The parent container context.
///
diff --git a/src/Registration/Fluent/BaseDecoratorConfigurator.cs b/src/Registration/Fluent/BaseDecoratorConfigurator.cs
index 439dec57..1897cdf0 100644
--- a/src/Registration/Fluent/BaseDecoratorConfigurator.cs
+++ b/src/Registration/Fluent/BaseDecoratorConfigurator.cs
@@ -24,10 +24,17 @@ internal BaseDecoratorConfigurator(Type serviceType, Type implementationType) :
///
/// Sets a decorated target condition for the registration.
///
- /// The type of the parent.
+ /// The type of the decorated service.
/// The configurator itself.
public TConfigurator WhenDecoratedServiceIs(Type targetType) => this.When(t => t.Type == targetType);
+ ///
+ /// Sets a decorated target condition for the registration.
+ ///
+ /// The name of the decorated service.
+ /// The configurator itself.
+ public TConfigurator WhenDecoratedServiceIs(object name) => this.When(typeInfo => name.Equals(typeInfo.DependencyName));
+
///
/// Sets an attribute condition the decorated target has to satisfy.
///
diff --git a/src/Registration/Fluent/BaseFluentConfigurator.cs b/src/Registration/Fluent/BaseFluentConfigurator.cs
index 208b1480..932b3dd0 100644
--- a/src/Registration/Fluent/BaseFluentConfigurator.cs
+++ b/src/Registration/Fluent/BaseFluentConfigurator.cs
@@ -83,23 +83,22 @@ public TConfigurator WithLifetime(LifetimeDescriptor lifetime)
public TConfigurator InScopeDefinedBy() => this.WithLifetime(Lifetimes.NamedScope(typeof(TScopeDefiner)));
///
- /// Binds a constructor or method parameter to a named registration, so the container will perform a named resolution on the bound dependency.
+ /// Binds a constructor/method parameter or a property/field to a named registration, so the container will perform a named resolution on the bound dependency.
///
/// The name of the bound named registration.
/// The configurator itself.
- public TConfigurator WithDependencyBinding(object dependencyName) =>
+ public TConfigurator WithDependencyBinding(object dependencyName = null) =>
this.WithDependencyBinding(typeof(TDependency), dependencyName);
///
- /// Binds a constructor or method parameter to a named registration, so the container will perform a named resolution on the bound dependency.
+ /// Binds a constructor/method parameter or a property/field to a named registration, so the container will perform a named resolution on the bound dependency.
///
/// The type of the dependency to search for.
/// The name of the bound named registration.
/// The fluent configurator.
- public TConfigurator WithDependencyBinding(Type dependencyType, object dependencyName)
+ public TConfigurator WithDependencyBinding(Type dependencyType, object dependencyName = null)
{
Shield.EnsureNotNull(dependencyType, nameof(dependencyType));
- Shield.EnsureNotNull(dependencyName, nameof(dependencyName));
this.Context.DependencyBindings.Add(dependencyType, dependencyName);
@@ -107,15 +106,14 @@ public TConfigurator WithDependencyBinding(Type dependencyType, object dependenc
}
///
- /// Binds a constructor or method parameter to a named registration, so the container will perform a named resolution on the bound dependency.
+ /// Binds a constructor/method parameter or a property/field to a named registration, so the container will perform a named resolution on the bound dependency.
///
/// The parameter name of the dependency to search for.
/// The name of the bound named registration.
/// The fluent configurator.
- public TConfigurator WithDependencyBinding(string parameterName, object dependencyName)
+ public TConfigurator WithDependencyBinding(string parameterName, object dependencyName = null)
{
Shield.EnsureNotNull(parameterName, nameof(parameterName));
- Shield.EnsureNotNull(dependencyName, nameof(dependencyName));
this.Context.DependencyBindings.Add(parameterName, dependencyName);
@@ -136,7 +134,9 @@ public TConfigurator WithDependencyBinding(string parameterName, object dependen
/// The configurator itself.
public TConfigurator WhenDependantIs(Type targetType)
{
- this.Context.TargetTypeCondition = targetType;
+ Shield.EnsureNotNull(targetType, nameof(targetType));
+
+ this.Context.TargetTypeConditions.Add(targetType);
return (TConfigurator)this;
}
@@ -154,8 +154,7 @@ public TConfigurator WhenDependantIs(Type targetType)
/// The configurator itself.
public TConfigurator WhenHas(Type attributeType)
{
- var store = (ExpandableArray)this.Context.AttributeConditions;
- store.Add(attributeType);
+ this.Context.AttributeConditions.Add(attributeType);
return (TConfigurator)this;
}
@@ -166,7 +165,7 @@ public TConfigurator WhenHas(Type attributeType)
/// The configurator itself.
public TConfigurator When(Func resolutionCondition)
{
- this.Context.ResolutionCondition = resolutionCondition;
+ this.Context.ResolutionConditions.Add(resolutionCondition);
return (TConfigurator)this;
}
@@ -177,8 +176,7 @@ public TConfigurator When(Func resolutionCondition)
/// The fluent configurator.
public TConfigurator WithInjectionParameters(params KeyValuePair[] injectionParameters)
{
- var store = (ExpandableArray>)this.Context.InjectionParameters;
- store.AddRange(injectionParameters);
+ this.Context.InjectionParameters.AddRange(injectionParameters);
return (TConfigurator)this;
}
@@ -190,8 +188,7 @@ public TConfigurator WithInjectionParameters(params KeyValuePair
/// The fluent configurator.
public TConfigurator WithInjectionParameter(string name, object value)
{
- var store = (ExpandableArray>)this.Context.InjectionParameters;
- store.Add(new KeyValuePair(name, value));
+ this.Context.InjectionParameters.Add(new KeyValuePair(name, value));
return (TConfigurator)this;
}
@@ -260,11 +257,9 @@ public TConfigurator WithConstructorByArguments(params object[] arguments)
/// The name of the member.
/// The name of the dependency.
/// The fluent configurator.
- public TConfigurator InjectMember(string memberName, object dependencyName = null)
- {
- this.Context.InjectionMemberNames.Add(memberName, dependencyName);
- return (TConfigurator)this;
- }
+ [Obsolete("Use WithDependencyBinding() instead.")]
+ public TConfigurator InjectMember(string memberName, object dependencyName = null) =>
+ this.WithDependencyBinding(memberName, dependencyName);
///
/// Tells the container that it shouldn't track the resolved transient object for disposal.
@@ -296,6 +291,13 @@ public TConfigurator ReplaceOnlyIfExists()
return (TConfigurator)this;
}
+ private protected void SetFactory(Delegate factory, bool isCompiledLambda, params Type[] parameterTypes)
+ {
+ this.Context.Factory = factory;
+ this.Context.FactoryParameters = parameterTypes;
+ this.Context.IsFactoryDelegateACompiledLambda = isCompiledLambda;
+ }
+
private static void ThrowConstructorNotFoundException(Type type, params Type[] argTypes)
{
throw argTypes.Length switch
diff --git a/src/Registration/Fluent/DecoratorConfigurator.cs b/src/Registration/Fluent/DecoratorConfigurator.cs
index b7115b33..e4deec2a 100644
--- a/src/Registration/Fluent/DecoratorConfigurator.cs
+++ b/src/Registration/Fluent/DecoratorConfigurator.cs
@@ -1,4 +1,5 @@
-using System;
+using Stashbox.Utils;
+using System;
using System.Linq.Expressions;
namespace Stashbox.Registration.Fluent
@@ -17,9 +18,13 @@ internal DecoratorConfigurator(Type serviceType, Type implementationType) : base
}
///
- public DecoratorConfigurator InjectMember(Expression> expression, object dependencyName = null)
+ public DecoratorConfigurator InjectMember(Expression> expression, object dependencyName = null) =>
+ this.WithDependencyBinding(expression, dependencyName);
+
+ ///
+ public DecoratorConfigurator WithDependencyBinding(Expression> expression, object dependencyName = null)
{
- this.compositor.InjectMember(expression, dependencyName);
+ this.compositor.WithDependencyBinding(expression, dependencyName);
return this;
}
@@ -38,16 +43,51 @@ public DecoratorConfigurator WithInitializer(Action
- public DecoratorConfigurator WithFactory(Func singleFactory, bool isCompiledLambda = false)
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
+ {
+ this.compositor.WithFactory(factory, isCompiledLambda);
+ return this;
+ }
+
+ ///
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
+ {
+ this.compositor.WithFactory(factory, isCompiledLambda);
+ return this;
+ }
+
+ ///
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
+ {
+ this.compositor.WithFactory(factory, isCompiledLambda);
+ return this;
+ }
+
+ ///
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
+ {
+ this.compositor.WithFactory(factory, isCompiledLambda);
+ return this;
+ }
+
+ ///
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
{
- this.compositor.WithFactory(singleFactory, isCompiledLambda);
+ this.compositor.WithFactory(factory, isCompiledLambda);
return this;
}
///
- public DecoratorConfigurator WithFactory(Func containerFactory, bool isCompiledLambda = false)
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
{
- this.compositor.WithFactory(containerFactory, isCompiledLambda);
+ this.compositor.WithFactory(factory, isCompiledLambda);
+ return this;
+ }
+
+ ///
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
+ {
+ this.compositor.WithFactory(factory, isCompiledLambda);
return this;
}
}
@@ -63,26 +103,23 @@ internal DecoratorConfigurator(Type serviceType, Type implementationType) : base
///
/// Sets a container factory delegate for the registration.
///
- /// The container factory delegate.
+ /// The container factory delegate.
/// Flag that indicates the passed factory delegate is a compiled lambda from .
/// The configurator itself.
- public DecoratorConfigurator WithFactory(Func containerFactory, bool isCompiledLambda = false)
+ public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false)
{
- this.Context.ContainerFactory = containerFactory;
- this.Context.IsFactoryDelegateACompiledLambda = isCompiledLambda;
+ this.SetFactory(factory, isCompiledLambda, Constants.ResolverType, Constants.ObjectType);
return this;
}
-
///
/// Sets a parameter-less factory delegate for the registration.
///
- /// The factory delegate.
+ /// The factory delegate.
/// Flag that indicates the passed factory delegate is a compiled lambda from .
/// The configurator itself.
- public DecoratorConfigurator WithFactory(Func
public Func AutoMemberInjectionFilter { get; internal set; }
+ internal ExpandableArray TargetTypeConditions { get; set; }
+ internal ExpandableArray> ResolutionConditions { get; set; }
+ internal ExpandableArray AttributeConditions { get; set; }
+ internal bool ReplaceExistingRegistration { get; set; }
+ internal bool ReplaceExistingRegistrationOnlyIfExists { get; set; }
+ internal ExpandableArray AdditionalServiceTypes { get; set; }
+ internal ExpandableArray> InjectionParameters { get; set; }
+
internal RegistrationContext()
{
this.AttributeConditions = new ExpandableArray();
+ this.TargetTypeConditions = new ExpandableArray();
+ this.ResolutionConditions = new ExpandableArray>();
this.AdditionalServiceTypes = new ExpandableArray();
this.InjectionParameters = new ExpandableArray>();
- this.InjectionMemberNames = new Dictionary();
this.DependencyBindings = new Dictionary();
}
}
diff --git a/src/Registration/RegistrationRepository.cs b/src/Registration/RegistrationRepository.cs
index dc9e0c19..21a3b075 100644
--- a/src/Registration/RegistrationRepository.cs
+++ b/src/Registration/RegistrationRepository.cs
@@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
namespace Stashbox.Registration
{
@@ -107,11 +106,9 @@ public bool ContainsRegistration(Type type, object name) =>
public IEnumerable> GetRegistrationMappings() =>
serviceRepository.Walk().SelectMany(reg => reg.Value.Select(r => new KeyValuePair(reg.Key, r)));
- public ServiceRegistration GetRegistrationOrDefault(Type type, ResolutionContext resolutionContext, object name = null) =>
- this.GetRegistrationsForType(type)?.SelectOrDefault(new TypeInformation(type, name), resolutionContext, this.topLevelFilters);
-
public ServiceRegistration GetRegistrationOrDefault(TypeInformation typeInfo, ResolutionContext resolutionContext) =>
- this.GetRegistrationsForType(typeInfo.Type)?.SelectOrDefault(typeInfo, resolutionContext, this.filters);
+ this.GetRegistrationsForType(typeInfo.Type)?.SelectOrDefault(typeInfo, resolutionContext,
+ resolutionContext.IsTopRequest ? this.topLevelFilters : this.filters);
public IEnumerable GetRegistrationsOrDefault(TypeInformation typeInfo, ResolutionContext resolutionContext) =>
this.GetRegistrationsForType(typeInfo.Type)
diff --git a/src/Registration/ServiceRegistration.cs b/src/Registration/ServiceRegistration.cs
index 96d451d7..9ed4d0ee 100644
--- a/src/Registration/ServiceRegistration.cs
+++ b/src/Registration/ServiceRegistration.cs
@@ -79,8 +79,9 @@ internal ServiceRegistration(Type implementationType, RegistrationType registrat
this.NamedScopeRestrictionIdentifier = lifetime.ScopeName;
}
- this.HasCondition = this.RegistrationContext.TargetTypeCondition != null || this.RegistrationContext.ResolutionCondition != null ||
- this.RegistrationContext.AttributeConditions != null && this.RegistrationContext.AttributeConditions.Any();
+ this.HasCondition = this.RegistrationContext.TargetTypeConditions.Length > 0 ||
+ this.RegistrationContext.ResolutionConditions.Length > 0 ||
+ this.RegistrationContext.AttributeConditions.Length > 0;
this.RegistrationId = ReserveRegistrationId();
this.RegistrationOrder = ReserveRegistrationOrder();
@@ -98,14 +99,26 @@ internal void Replaces(ServiceRegistration serviceRegistration) =>
this.RegistrationOrder = serviceRegistration.RegistrationOrder;
private bool HasParentTypeConditionAndMatch(TypeInformation typeInfo) =>
- this.RegistrationContext.TargetTypeCondition != null && typeInfo.ParentType != null && this.RegistrationContext.TargetTypeCondition == typeInfo.ParentType;
+ this.RegistrationContext.TargetTypeConditions.Length > 0 && typeInfo.ParentType != null && this.RegistrationContext.TargetTypeConditions.Contains(typeInfo.ParentType);
private bool HasAttributeConditionAndMatch(TypeInformation typeInfo) =>
- this.RegistrationContext.AttributeConditions != null && typeInfo.CustomAttributes != null &&
+ this.RegistrationContext.AttributeConditions.Length > 0 && typeInfo.CustomAttributes != null &&
this.RegistrationContext.AttributeConditions.Intersect(typeInfo.CustomAttributes.Select(attribute => attribute.GetType())).Any();
- private bool HasResolutionConditionAndMatch(TypeInformation typeInfo) =>
- this.RegistrationContext.ResolutionCondition != null && this.RegistrationContext.ResolutionCondition(typeInfo);
+ private bool HasResolutionConditionAndMatch(TypeInformation typeInfo)
+ {
+ if (this.RegistrationContext.ResolutionConditions.Length == 0)
+ return false;
+
+ var length = this.RegistrationContext.ResolutionConditions.Length;
+ for (int i = 0; i < length; i++)
+ {
+ if(this.RegistrationContext.ResolutionConditions[i](typeInfo))
+ return true;
+ }
+
+ return false;
+ }
private static int ReserveRegistrationId() =>
Interlocked.Increment(ref globalRegistrationId);
diff --git a/src/Registration/ServiceRegistrator.cs b/src/Registration/ServiceRegistrator.cs
index 0d06063f..b4fd6136 100644
--- a/src/Registration/ServiceRegistrator.cs
+++ b/src/Registration/ServiceRegistrator.cs
@@ -7,8 +7,8 @@ internal class ServiceRegistrator
{
public void Register(IContainerContext containerContext, ServiceRegistration serviceRegistration, Type serviceType)
{
- if (serviceRegistration.RegistrationContext.AdditionalServiceTypes.Any())
- foreach (var additionalServiceType in serviceRegistration.RegistrationContext.AdditionalServiceTypes)
+ if (serviceRegistration.RegistrationContext.AdditionalServiceTypes.Length > 0)
+ foreach (var additionalServiceType in serviceRegistration.RegistrationContext.AdditionalServiceTypes.Distinct())
this.RegisterInternal(containerContext, serviceRegistration, additionalServiceType);
this.RegisterInternal(containerContext, serviceRegistration, serviceType);
@@ -27,8 +27,8 @@ private void RegisterInternal(IContainerContext containerContext, ServiceRegistr
public void ReMap(IContainerContext containerContext, ServiceRegistration serviceRegistration, Type serviceType)
{
- if (serviceRegistration.RegistrationContext.AdditionalServiceTypes.Any())
- foreach (var additionalServiceType in serviceRegistration.RegistrationContext.AdditionalServiceTypes)
+ if (serviceRegistration.RegistrationContext.AdditionalServiceTypes.Length > 0)
+ foreach (var additionalServiceType in serviceRegistration.RegistrationContext.AdditionalServiceTypes.Distinct())
this.ReMapInternal(containerContext, serviceRegistration, additionalServiceType);
this.ReMapInternal(containerContext, serviceRegistration, serviceType);
diff --git a/src/Resolution/ResolutionContext.cs b/src/Resolution/ResolutionContext.cs
index cfdd203c..87dede9a 100644
--- a/src/Resolution/ResolutionContext.cs
+++ b/src/Resolution/ResolutionContext.cs
@@ -20,7 +20,6 @@ public class ResolutionContext
private readonly Utils.Data.Stack circularDependencyBarrier;
internal IContainerContext RequestInitiatorContainerContext { get; }
- internal IResolutionStrategy ResolutionStrategy { get; }
internal ExpandableArray SingleInstructions { get; private set; }
internal Tree DefinedVariables { get; private set; }
internal ExpandableArray[]> ParameterExpressions { get; private set; }
@@ -33,6 +32,7 @@ public class ResolutionContext
internal bool FactoryDelegateCacheEnabled { get; }
internal Utils.Data.Stack ScopeNames { get; }
internal bool IsValidationRequest { get; }
+ internal bool IsTopRequest { get; set; }
///
/// True if null result is allowed, otherwise false.
@@ -56,7 +56,6 @@ public class ResolutionContext
internal ResolutionContext(IEnumerable initialScopeNames,
IContainerContext currentContainerContext,
- IResolutionStrategy resolutionStrategy,
bool isRequestedFromRoot,
bool nullResultAllowed = false,
bool isValidationRequest = false,
@@ -79,10 +78,10 @@ internal ResolutionContext(IEnumerable initialScopeNames,
this.circularDependencyBarrier = new Utils.Data.Stack();
this.expressionCache = new Tree();
this.factoryCache = new Tree>();
- this.ResolutionStrategy = resolutionStrategy;
this.IsRequestedFromRoot = isRequestedFromRoot;
this.CurrentContainerContext = this.RequestInitiatorContainerContext = currentContainerContext;
this.FactoryDelegateCacheEnabled = this.PerResolutionRequestCacheEnabled = dependencyOverrides == null;
+ this.IsTopRequest = true;
}
///
diff --git a/src/Resolution/ResolutionStrategy.cs b/src/Resolution/ResolutionStrategy.cs
index e726b0c8..ed14b1d8 100644
--- a/src/Resolution/ResolutionStrategy.cs
+++ b/src/Resolution/ResolutionStrategy.cs
@@ -27,28 +27,31 @@ public Expression BuildExpressionForType(ResolutionContext resolutionContext, Ty
if (typeInformation.Type == Constants.ResolverType)
return resolutionContext.CurrentScopeParameter;
- if (resolutionContext.ParameterExpressions.Length > 0)
+ if (!resolutionContext.IsTopRequest)
{
- var type = typeInformation.Type;
- var length = resolutionContext.ParameterExpressions.Length;
- for (var i = length; i-- > 0;)
+ if (resolutionContext.ParameterExpressions.Length > 0)
{
- var parameters = resolutionContext.ParameterExpressions[i]
- .WhereOrDefault(p => p.I2.Type == type ||
- p.I2.Type.Implements(type));
-
- if (parameters == null) continue;
- var selected =
- parameters.FirstOrDefault(parameter => !parameter.I1) ?? parameters[parameters.Length - 1];
- selected.I1 = true;
- return selected.I2;
+ var type = typeInformation.Type;
+ var length = resolutionContext.ParameterExpressions.Length;
+ for (var i = length; i-- > 0;)
+ {
+ var parameters = resolutionContext.ParameterExpressions[i]
+ .WhereOrDefault(p => p.I2.Type == type ||
+ p.I2.Type.Implements(type));
+
+ if (parameters == null) continue;
+ var selected =
+ parameters.FirstOrDefault(parameter => !parameter.I1) ?? parameters[parameters.Length - 1];
+ selected.I1 = true;
+ return selected.I2;
+ }
}
- }
- var decorators = resolutionContext.Decorators.GetOrDefault(typeInformation.Type, false);
- if (decorators != null)
- return this.BuildExpressionForDecorator(decorators.Pop(),
- resolutionContext.BeginDecoratingContext(typeInformation.Type, decorators), typeInformation.Type, decorators);
+ var decorators = resolutionContext.Decorators.GetOrDefault(typeInformation.Type, true);
+ if (decorators != null)
+ return this.BuildExpressionForDecorator(decorators.Pop(),
+ resolutionContext.BeginDecoratingContext(typeInformation.Type, decorators), typeInformation.Type, decorators);
+ }
var exprOverride = resolutionContext.GetExpressionOverrideOrDefault(typeInformation.Type, typeInformation.DependencyName);
if (exprOverride != null)
@@ -59,6 +62,7 @@ public Expression BuildExpressionForType(ResolutionContext resolutionContext, Ty
.RegistrationRepository
.GetRegistrationOrDefault(typeInformation, resolutionContext);
+ resolutionContext.IsTopRequest = false;
return registration != null
? this.BuildExpressionForRegistration(registration, resolutionContext, typeInformation)
: this.BuildResolutionExpressionUsingResolvers(typeInformation, resolutionContext);
@@ -76,7 +80,7 @@ public IEnumerable BuildExpressionsForEnumerableRequest(ResolutionCo
return registrations.Select(reg =>
{
- var decorators = resolutionContext.Decorators.GetOrDefault(typeInformation.Type, false);
+ var decorators = resolutionContext.Decorators.GetOrDefault(typeInformation.Type, true);
if (decorators == null)
return this.BuildExpressionForRegistration(reg, resolutionContext, typeInformation);
@@ -87,25 +91,6 @@ public IEnumerable BuildExpressionsForEnumerableRequest(ResolutionCo
});
}
- public Expression BuildExpressionForTopLevelRequest(Type type, object name, ResolutionContext resolutionContext)
- {
- if (type == Constants.ResolverType)
- return resolutionContext.CurrentScopeParameter;
-
- var exprOverride = resolutionContext.GetExpressionOverrideOrDefault(type, name);
- if (exprOverride != null)
- return exprOverride;
-
- var registration = resolutionContext
- .CurrentContainerContext
- .RegistrationRepository
- .GetRegistrationOrDefault(type, resolutionContext, name);
-
- return registration != null
- ? this.BuildExpressionForRegistration(registration, resolutionContext, new TypeInformation(type, name))
- : this.BuildResolutionExpressionUsingResolvers(new TypeInformation(type, name), resolutionContext);
- }
-
public Expression BuildExpressionForRegistration(ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation)
{
@@ -127,7 +112,7 @@ public Expression BuildExpressionForRegistration(ServiceRegistration serviceRegi
resolutionContext.BeginDecoratingContext(requestedType, stack), requestedType, stack);
}
- public Expression BuildResolutionExpressionUsingResolvers(TypeInformation typeInfo, ResolutionContext resolutionContext)
+ private Expression BuildResolutionExpressionUsingResolvers(TypeInformation typeInfo, ResolutionContext resolutionContext)
{
var expression = this.resolverRepository.BuildResolutionExpression(typeInfo, resolutionContext, this);
if (expression != null) return expression;
diff --git a/src/ResolutionScope.Resolver.cs b/src/ResolutionScope.Resolver.cs
index 0ae5043d..65e3a781 100644
--- a/src/ResolutionScope.Resolver.cs
+++ b/src/ResolutionScope.Resolver.cs
@@ -20,13 +20,13 @@ public object Resolve(Type typeFrom, bool nullResultAllowed = false, object[] de
if (dependencyOverrides != null)
return this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
- this.resolutionStrategy, this == this.containerContext.RootScope, nullResultAllowed, false,
+ this == this.containerContext.RootScope, nullResultAllowed, false,
this.ProcessDependencyOverrides(dependencyOverrides)), typeFrom);
var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefault(typeFrom);
return cachedFactory != null
? cachedFactory(this)
- : this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext, this.resolutionStrategy,
+ : this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
this == this.containerContext.RootScope, nullResultAllowed, false,
this.ProcessDependencyOverrides(dependencyOverrides)), typeFrom);
}
@@ -36,14 +36,14 @@ public object Resolve(Type typeFrom, object name, bool nullResultAllowed = false
this.ThrowIfDisposed();
if (dependencyOverrides != null)
- return this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext, this.resolutionStrategy,
+ return this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
this == this.containerContext.RootScope, nullResultAllowed, false,
this.ProcessDependencyOverrides(dependencyOverrides)), typeFrom, name);
var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefault(name, false);
return cachedFactory != null
? cachedFactory(this)
- : this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext, this.resolutionStrategy,
+ : this.Activate(new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
this == this.containerContext.RootScope, nullResultAllowed, false,
this.ProcessDependencyOverrides(dependencyOverrides)), typeFrom, name);
}
@@ -72,8 +72,7 @@ public TTo BuildUp(TTo instance)
{
this.ThrowIfDisposed();
- var resolutionContext = new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
- this.resolutionStrategy, this == this.containerContext.RootScope);
+ var resolutionContext = new ResolutionContext(this.GetActiveScopeNames(), this.containerContext, this == this.containerContext.RootScope);
var expression = this.expressionFactory.ConstructBuildUpExpression(resolutionContext, instance.AsConstant(), typeof(TTo));
return (TTo)expression.CompileDelegate(resolutionContext, this.containerContext.ContainerConfiguration)(this);
}
@@ -85,8 +84,7 @@ public object Activate(Type type, params object[] arguments)
if (!type.IsResolvableType())
throw new ArgumentException($"The given type ({type.FullName}) could not be activated on the fly by the container.");
- var resolutionContext = new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
- this.resolutionStrategy, this == this.containerContext.RootScope,
+ var resolutionContext = new ResolutionContext(this.GetActiveScopeNames(), this.containerContext, this == this.containerContext.RootScope,
dependencyOverrides: this.ProcessDependencyOverrides(arguments));
var expression = this.expressionFactory.ConstructExpression(resolutionContext, type);
return expression.CompileDelegate(resolutionContext, this.containerContext.ContainerConfiguration)(this);
@@ -94,7 +92,8 @@ public object Activate(Type type, params object[] arguments)
private object Activate(ResolutionContext resolutionContext, Type type, object name = null)
{
- var expression = this.resolutionStrategy.BuildExpressionForTopLevelRequest(type, name, resolutionContext);
+ var expression = this.containerContext.ResolutionStrategy
+ .BuildExpressionForType(resolutionContext, new TypeInformation(type, name));
if (expression == null)
if (resolutionContext.NullResultAllowed)
return null;
@@ -113,10 +112,11 @@ private object Activate(ResolutionContext resolutionContext, Type type, object n
private Delegate ActivateFactoryDelegate(Type type, Type[] parameterTypes, object name, bool nullResultAllowed)
{
var resolutionContext = new ResolutionContext(this.GetActiveScopeNames(), this.containerContext,
- this.resolutionStrategy, this == this.containerContext.RootScope, nullResultAllowed,
+ this == this.containerContext.RootScope, nullResultAllowed,
initialParameters: parameterTypes.AsParameters());
- var initExpression = this.resolutionStrategy.BuildExpressionForTopLevelRequest(type, name, resolutionContext);
+ var initExpression = this.containerContext.ResolutionStrategy
+ .BuildExpressionForType(resolutionContext, new TypeInformation(type, name));
if (initExpression == null)
if (resolutionContext.NullResultAllowed)
return null;
diff --git a/src/ResolutionScope.cs b/src/ResolutionScope.cs
index 52340fb3..cf94b9c6 100644
--- a/src/ResolutionScope.cs
+++ b/src/ResolutionScope.cs
@@ -63,7 +63,6 @@ private object WaitForEvaluation()
}
}
- private readonly ResolutionStrategy resolutionStrategy;
private readonly ExpressionFactory expressionFactory;
private readonly IContainerContext containerContext;
private readonly DelegateCacheProvider delegateCacheProvider;
@@ -77,11 +76,9 @@ private object WaitForEvaluation()
public IResolutionScope ParentScope { get; }
- private ResolutionScope(ResolutionStrategy resolutionStrategy,
- ExpressionFactory expressionBuilder, IContainerContext containerContext,
+ private ResolutionScope(ExpressionFactory expressionBuilder, IContainerContext containerContext,
DelegateCacheProvider delegateCacheProvider, object name)
{
- this.resolutionStrategy = resolutionStrategy;
this.expressionFactory = expressionBuilder;
this.containerContext = containerContext;
this.Name = name;
@@ -92,15 +89,13 @@ private ResolutionScope(ResolutionStrategy resolutionStrategy,
: delegateCacheProvider.GetNamedCache(name);
}
- internal ResolutionScope(ResolutionStrategy resolutionStrategy,
- ExpressionFactory expressionBuilder, IContainerContext containerContext)
- : this(resolutionStrategy, expressionBuilder, containerContext,
- new DelegateCacheProvider(), null)
+ internal ResolutionScope(ExpressionFactory expressionBuilder, IContainerContext containerContext)
+ : this(expressionBuilder, containerContext, new DelegateCacheProvider(), null)
{ }
- private ResolutionScope(ResolutionStrategy resolutionStrategy, ExpressionFactory expressionBuilder,
- IContainerContext containerContext, IResolutionScope parent, DelegateCacheProvider delegateCacheProvider, object name = null)
- : this(resolutionStrategy, expressionBuilder, containerContext, delegateCacheProvider, name)
+ private ResolutionScope(ExpressionFactory expressionBuilder, IContainerContext containerContext,
+ IResolutionScope parent, DelegateCacheProvider delegateCacheProvider, object name = null)
+ : this(expressionBuilder, containerContext, delegateCacheProvider, name)
{
this.ParentScope = parent;
}
@@ -109,7 +104,7 @@ public IDependencyResolver BeginScope(object name = null, bool attachToParent =
{
this.ThrowIfDisposed();
- var scope = new ResolutionScope(this.resolutionStrategy, this.expressionFactory,
+ var scope = new ResolutionScope(this.expressionFactory,
this.containerContext, this, this.delegateCacheProvider, name);
return attachToParent ? this.AddDisposableTracking(scope) : scope;
diff --git a/src/StashboxContainer.cs b/src/StashboxContainer.cs
index 27032c2d..0380fd4c 100644
--- a/src/StashboxContainer.cs
+++ b/src/StashboxContainer.cs
@@ -86,7 +86,7 @@ public bool CanResolve(Type typeFrom, object name = null)
return this.ContainerContext.RegistrationRepository.ContainsRegistration(typeFrom, name) ||
this.resolutionStrategy.CanResolveType(new TypeInformation(typeFrom, name),
- new ResolutionContext(this.ContainerContext.RootScope.GetActiveScopeNames(), this.ContainerContext, this.resolutionStrategy, false));
+ new ResolutionContext(this.ContainerContext.RootScope.GetActiveScopeNames(), this.ContainerContext, false));
}
///
@@ -116,7 +116,7 @@ public void Validate()
{
this.resolutionStrategy.BuildExpressionForRegistration(serviceRegistration.Value,
new ResolutionContext(this.ContainerContext.RootScope.GetActiveScopeNames(),
- this.ContainerContext, this.resolutionStrategy, false, false, true),
+ this.ContainerContext, false, false, true),
new TypeInformation(serviceRegistration.Key, serviceRegistration.Value.RegistrationContext.Name));
}
catch (Exception ex)
diff --git a/src/Utils/Swap.cs b/src/Utils/Swap.cs
index ea68e75a..98e9d12a 100644
--- a/src/Utils/Swap.cs
+++ b/src/Utils/Swap.cs
@@ -29,7 +29,7 @@ private static bool RepeatSwap(ref TValue refValue, Func
var desiredThreshold = Environment.ProcessorCount * 6;
var swapThreshold = desiredThreshold <= MinimumSwapThreshold ? MinimumSwapThreshold : desiredThreshold;
- while(true)
+ while (true)
{
var currentValue = refValue;
var newValue = valueFactory(t1, t2, t3, t4, currentValue);
@@ -42,8 +42,7 @@ private static bool RepeatSwap(ref TValue refValue, Func
if (++counter > swapThreshold)
throw new InvalidOperationException("Swap quota exceeded.");
- if (counter > 20)
- wait.SpinOnce();
+ wait.SpinOnce();
}
}
}
diff --git a/test/CircularDependencyTests.cs b/test/CircularDependencyTests.cs
index 5f7d7716..beb7d55e 100644
--- a/test/CircularDependencyTests.cs
+++ b/test/CircularDependencyTests.cs
@@ -120,6 +120,14 @@ public void CircularDependencyTests_Runtime()
Assert.Throws(() => container.Resolve());
}
+ [Fact]
+ public void CircularDependencyTests_Runtime_Parameterized_Factory()
+ {
+ using var container = new StashboxContainer();
+ container.Register(config => config.WithFactory(t => (Test1)t));
+ Assert.Throws(() => container.Resolve());
+ }
+
#if HAS_ASYNC_DISPOSABLE
[Fact]
public async Task CircularDependencyTests_Runtime_Async()
@@ -128,6 +136,14 @@ public async Task CircularDependencyTests_Runtime_Async()
container.Register(config => config.WithFactory(r => (Test1)r.Resolve()));
Assert.Throws(() => container.Resolve());
}
+
+ [Fact]
+ public async Task CircularDependencyTests_Runtime_Async_Parameterized_Factory()
+ {
+ await using var container = new StashboxContainer();
+ container.Register(config => config.WithFactory(t => (Test1)t));
+ Assert.Throws(() => container.Resolve());
+ }
#endif
interface ITest1 { }
diff --git a/test/ConditionalTests.cs b/test/ConditionalTests.cs
index 561266ca..34ab0eaf 100644
--- a/test/ConditionalTests.cs
+++ b/test/ConditionalTests.cs
@@ -202,6 +202,40 @@ public void ConditionalTests_AttributeCondition_Third_NonGeneric()
Assert.IsType(test3.test12);
}
+ [Fact]
+ public void ConditionalTests_Combined()
+ {
+ var container = new StashboxContainer();
+ container.Register(context => context.WhenDependantIs().WhenDependantIs());
+ container.Register();
+ container.Register();
+ container.Register();
+ container.Register();
+
+ var t1 = container.Resolve();
+ var t2 = container.Resolve();
+
+ Assert.IsType(t1.Test);
+ Assert.IsType(t2.Test);
+ }
+
+ [Fact]
+ public void ConditionalTests_Combined_Common()
+ {
+ var container = new StashboxContainer();
+ container.Register(context => context.When(t => t.ParentType == typeof(Test4)).When(t => t.ParentType == typeof(Test5)));
+ container.Register();
+ container.Register();
+ container.Register();
+ container.Register();
+
+ var t1 = container.Resolve();
+ var t2 = container.Resolve();
+
+ Assert.IsType(t1.Test);
+ Assert.IsType(t2.Test);
+ }
+
interface ITest1 { }
interface ITest2 { ITest1 test1 { get; set; } ITest1 test12 { get; set; } }
@@ -240,6 +274,26 @@ public Test3([TestCondition2]ITest1 test12)
}
}
+ class Test4
+ {
+ public Test4(ITest1 test)
+ {
+ Test = test;
+ }
+
+ public ITest1 Test { get; }
+ }
+
+ class Test5
+ {
+ public Test5(ITest1 test)
+ {
+ Test = test;
+ }
+
+ public ITest1 Test { get; }
+ }
+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter)]
class TestConditionAttribute : Attribute
{
diff --git a/test/DecoratorTests.cs b/test/DecoratorTests.cs
index 1dba27ca..df882067 100644
--- a/test/DecoratorTests.cs
+++ b/test/DecoratorTests.cs
@@ -195,7 +195,7 @@ public void DecoratorTests_Inject_Member_With_Config(CompilerType compilerType)
{
using var container = new StashboxContainer(c => c.WithCompiler(compilerType));
container.Register();
- container.RegisterDecorator(config => config.InjectMember("Test"));
+ container.RegisterDecorator(config => config.WithDependencyBinding("Test"));
var test = container.Resolve();
Assert.NotNull(test);
@@ -587,6 +587,27 @@ public void DecoratorTests_Conditional_Named(CompilerType compilerType)
Assert.IsType(t2.Test.Test);
}
+ [Theory]
+ [ClassData(typeof(CompilerTypeTestData))]
+ public void DecoratorTests_Conditional_Named_Short(CompilerType compilerType)
+ {
+ using var container = new StashboxContainer(c => c.WithCompiler(compilerType));
+ container.Register("t1");
+ container.Register("t2");
+ container.RegisterDecorator();
+ container.RegisterDecorator(c => c.WhenDecoratedServiceIs("t2"));
+
+ var t1 = container.Resolve("t1");
+ var t2 = container.Resolve("t2");
+
+ Assert.IsType(t1);
+ Assert.IsType(t1.Test);
+
+ Assert.IsType(t2);
+ Assert.IsType(t2.Test);
+ Assert.IsType(t2.Test.Test);
+ }
+
[Theory]
[ClassData(typeof(CompilerTypeTestData))]
public void DecoratorTests_Conditional_Parent(CompilerType compilerType)
@@ -868,6 +889,86 @@ public void DecoratorTests_WithFinalizer(CompilerType compilerType)
Assert.True(finalized);
}
+ [Theory]
+ [ClassData(typeof(CompilerTypeTestData))]
+ public void DecoratorTests_Factory_Param1(CompilerType compilerType)
+ {
+ using var container = new StashboxContainer(c => c.WithCompiler(compilerType));
+ container.Register();
+ container.RegisterDecorator(c => c.WithFactory(t1 =>
+ {
+ Assert.IsType(t1);
+ return new TestDecorator1(t1);
+ }));
+ container.Resolve();
+ }
+
+ [Theory]
+ [ClassData(typeof(CompilerTypeTestData))]
+ public void DecoratorTests_Factory_Param_NextDecorator(CompilerType compilerType)
+ {
+ using var container = new StashboxContainer(c => c.WithCompiler(compilerType));
+ container.Register();
+ container.RegisterDecorator();
+ container.RegisterDecorator(c => c.WithFactory(t1 =>
+ {
+ Assert.IsType(t1);
+ return new TestDecorator1(t1);
+ }));
+ container.Resolve();
+ }
+
+ [Theory]
+ [ClassData(typeof(CompilerTypeTestData))]
+ public void DecoratorTests_Factory_Param2(CompilerType compilerType)
+ {
+ using var container = new StashboxContainer(c => c.WithCompiler(compilerType));
+ container.Register();
+ container.Register(c => c.AsImplementedTypes());
+ container.RegisterDecorator