diff --git a/CHANGELOG.md b/CHANGELOG.md index fb33d48a..be69ee76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ 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). +## [v5.13.0] - 2023-11-18 +### Added +- .NET 8.0 target. +- [#134](https://github.com/z4kn4fein/stashbox/issues/134) Concept of [Auto lifetime](https://z4kn4fein.github.io/stashbox/docs/guides/lifetimes#auto-lifetime): + - It aligns to the lifetime of the resolved service's dependencies. When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary. +- Auto injection of `required` members. +- MS.DI compatibility features for supporting [keyed services](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#keyed-di-services): + - `DependencyName` attribute. When a parameter is marked with this attribute, the container will pass the given dependency's name to it. + - `WithUniversalName()` container configuration method. It sets the universal name which is a special name that allows named resolution work for any given name. + - `WithAdditionalDependencyNameAttribute()` container configuration method. It adds an attribute type that is considered a dependency name indicator just like the [`DependencyName` attribute](https://z4kn4fein.github.io/stashbox/docs/guides/service-resolution#attributes). + - `WithAdditionalDependencyAttribute()` container configuration method. It adds an attribute type that is considered a dependency indicator just like the [`Dependency` attribute](https://z4kn4fein.github.io/stashbox/docs/guides/service-resolution#attributes). + ## [v5.12.2] - 2023-09-05 ### Fixed - There was an issue where using decorators with instance registrations resulted in resolution failure. @@ -398,6 +410,7 @@ The validation was executed only at the expression tree building phase, so an al - Removed the legacy container extension functionality. - Removed the support of PCL v259. +[v5.13.0]: https://github.com/z4kn4fein/stashbox/compare/5.12.2...5.13.0 [v5.12.2]: https://github.com/z4kn4fein/stashbox/compare/5.12.1...5.12.2 [v5.12.1]: https://github.com/z4kn4fein/stashbox/compare/5.11.1...5.12.1 [v5.11.1]: https://github.com/z4kn4fein/stashbox/compare/5.11.0...5.11.1 diff --git a/README.md b/README.md index 43ed2b96..97fe8c51 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Stashbox is a lightweight, fast, and portable dependency injection framework for Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (pre-release) --- | --- |---------------------------------------------------------------------------------------------------------------------------------| --- -[![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=5.12.2)](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=5.13.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 - 🚀 Fast, thread-safe, and lock-free operations. diff --git a/docs/docs/configuration/container-configuration.md b/docs/docs/configuration/container-configuration.md index a7d032cd..86c57224 100644 --- a/docs/docs/configuration/container-configuration.md +++ b/docs/docs/configuration/container-configuration.md @@ -68,6 +68,7 @@ new StashboxContainer(options => options ## Auto member-injection With this option, you can enable or disable the auto member-injection without [attributes](/docs/guides/service-resolution#attributes). +
@@ -140,9 +141,26 @@ new StashboxContainer(options => options
+ +
-:::note -Member selection filter: `config.WithAutoMemberInjection(filter: member => member.Type != typeof(IJob))` +#### Member selection filter +You can pass your own member selection logic to control which members should be auto injected. + +
+
+ +```cs +new StashboxContainer(options => options + .WithAutoMemberInjection( + filter: member => member.Type != typeof(ILogger))); +``` + +
+
+ +:::info +Members defined with C# 11's [`required`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) keyword are automatically injected by the container. ::: ## Constructor selection @@ -328,6 +346,57 @@ new StashboxContainer(options => options +## Named service resolution + +
+ +### `WithUniversalName` +Sets the universal name that represents a special name which allows named resolution work for any given name. + +
+
+ +```cs +new StashboxContainer(options => options + .WithUniversalName("Any")); +``` + +
+
+ + +
+ +### `WithAdditionalDependencyNameAttribute` +Adds an attribute type that is considered a dependency name indicator just like the [`DependencyName` attribute](/docs/guides/service-resolution#attributes). + +
+
+ +```cs +new StashboxContainer(options => options + .WithAdditionalDependencyNameAttribute()); +``` + +
+
+ + +
+ +### `WithAdditionalDependencyAttribute` +Adds an attribute type that is considered a dependency indicator just like the [`Dependency` attribute](/docs/guides/service-resolution#attributes). + +
+
+ +```cs +new StashboxContainer(options => options + .WithAdditionalDependencyAttribute()); +``` + +
+
## Default value injection diff --git a/docs/docs/configuration/registration-configuration.md b/docs/docs/configuration/registration-configuration.md index 07c94e66..f7737ce7 100644 --- a/docs/docs/configuration/registration-configuration.md +++ b/docs/docs/configuration/registration-configuration.md @@ -374,7 +374,7 @@ container.Register(config => config
### `WithPerScopedRequestLifetime` -Sets the lifetime to `PerScopedRequestLifetime`. That means this registration will behave like a singleton within every scoped resolution request. +Sets the lifetime to `PerScopedRequestLifetime`. This lifetime will create a new instance between scoped services. This means that every scoped service will get a different instance but within their dependency tree it will behave as a singleton.
@@ -390,6 +390,40 @@ container.Register(options => options
+### `WithPerRequestLifetime` +Sets the lifetime to `PerRequestLifetime`. This lifetime will create a new instance between resolution requests. Within the request the same instance will be re-used. + +
+
+ +```cs +container.Register(options => options + .WithPerRequestLifetime()); +``` + +
+
+ + +
+ +### `WithAutoLifetime` +Sets the lifetime to auto lifetime. This lifetime aligns to the lifetime of the resolved service's dependencies. When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary. + +
+
+ +```cs +container.Register(options => options + .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); +``` + +
+
+ + +
+ ### `WithLifetime` Sets a custom lifetime for the registration. @@ -708,7 +742,9 @@ container.Register(options => options
- +:::info +Members defined with C# 11's [`required`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) keyword are automatically injected by the container. +::: ## Injection parameters diff --git a/docs/docs/getting-started/introduction.md b/docs/docs/getting-started/introduction.md index 4ae34da2..52b46efa 100644 --- a/docs/docs/getting-started/introduction.md +++ b/docs/docs/getting-started/introduction.md @@ -12,7 +12,7 @@ Stashbox and its extensions are distributed via [NuGet](https://www.nuget.org/pa You can install the package by typing the following into the Package Manager Console: ```powershell -Install-Package Stashbox -Version 5.12.2 +Install-Package Stashbox -Version 5.13.0 ``` @@ -20,7 +20,7 @@ Install-Package Stashbox -Version 5.12.2 You can install the package by using the dotnet cli: ```bash -dotnet add package Stashbox --version 5.12.2 +dotnet add package Stashbox --version 5.13.0 ``` @@ -28,7 +28,7 @@ dotnet add package Stashbox --version 5.12.2 You can add the package into the package references of your `.csproj`: ```xml - + ``` diff --git a/docs/docs/getting-started/overview.md b/docs/docs/getting-started/overview.md index ea82b8c8..f049c349 100644 --- a/docs/docs/getting-started/overview.md +++ b/docs/docs/getting-started/overview.md @@ -17,7 +17,7 @@ These are the latest available stable and pre-release versions: 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?v=5.12.2)](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=5.13.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 - 🚀 Fast, thread-safe, and lock-free operations. diff --git a/docs/docs/guides/lifetimes.md b/docs/docs/guides/lifetimes.md index e57974ef..2f73fd7a 100644 --- a/docs/docs/guides/lifetimes.md +++ b/docs/docs/guides/lifetimes.md @@ -156,10 +156,6 @@ It is the same as scoped lifetime, except the given service will be selected onl You can also let a service [define](/docs/guides/scopes#service-as-scope) its own named scope. During registration, this scope can be referred to by its name upon using a named scope lifetime. -:::note -Services with named scope lifetime are disposed when the related named scope is being disposed. -::: -
@@ -213,6 +209,10 @@ DbJobExecutor executor = scope.Resolve();
+:::note +Services with named scope lifetime are disposed when the related named scope is being disposed. +::: + ## Per-request lifetime @@ -249,6 +249,66 @@ container.Register(options => options +## Auto lifetime + + +
+ +The requested service's lifetime will align to the lifetime of its dependencies. When the requested service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary. + +
+
+ +```cs +container.Register(options => options + .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); +``` + +
+
+ + +
+ +If the requested service has auto lifetime with a scoped boundary and it has only transient dependencies, it'll inherit their transient lifetime. + +
+
+ +```cs +container.Register(); + +container.Register(options => options + .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); + +// job has transient lifetime. +var job = container.Resolve(); +``` + +
+
+ + +
+ +When there's a dependency with higher lifespan than the given boundary, the requested service will get the boundary lifetime. + +
+
+ +```cs +container.RegisterSingleton(); + +container.Register(options => options + .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); + +// job has scoped lifetime. +var job = container.Resolve(); +``` + +
+
+ ## Custom lifetime If you'd like to use a custom lifetime, you can create your implementation by inheriting either from `FactoryLifetimeDescriptor` or from `ExpressionLifetimeDescriptor`, depending on how do you want to manage the service instances. diff --git a/docs/docs/guides/service-resolution.md b/docs/docs/guides/service-resolution.md index 1d11898f..f4b0653d 100644 --- a/docs/docs/guides/service-resolution.md +++ b/docs/docs/guides/service-resolution.md @@ -19,6 +19,8 @@ Stashbox, by default, uses the constructor that has the most parameters it knows [Property/field injection](/docs/configuration/registration-configuration#property-field-injection) is also supported in cases where constructor injection is not applicable. +Members defined with C# 11's [`required`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) keyword are automatically injected by the container. + :::info [Constructor selection](/docs/configuration/container-configuration#constructor-selection) and [property/field injection](/docs/configuration/container-configuration#auto-member-injection) is also configurable container-wide. ::: @@ -97,6 +99,8 @@ Attributes can give you control over how Stashbox selects dependencies for a ser - **On a property/field**: first, it enables *auto-injection* on the marked property/field (even if it wasn't configured at registration explicitly), and just as with the method parameter, it allows [named resolution](/docs/getting-started/glossary#named-resolution). +**DependencyName attribute**: marks a parameter to let the container know that it must pass the given dependency's name to it. + **InjectionMethod attribute**: marks a method to be called when the requested service is instantiated. @@ -147,6 +151,24 @@ container.Register(); IJob job = container.Resolve(); ``` + + + +```cs +class DbBackup : IJob +{ + public string Name { get; set; } + + public DbBackup([DependencyName] string name) + { } +} + +container.Register("Backup"); + +// job.Name is "Backup". +IJob job = container.Resolve(); +``` + @@ -176,19 +198,84 @@ IJob job = container.Resolve(); :::caution -Attributes provide a more straightforward configuration, but using them also tightens the bond between your application and Stashbox. If you consider this an issue, the same functionality is available on the *registration API* as [dependency binding](/docs/guides/service-resolution#dependency-binding). +Attributes provide a more straightforward configuration, but using them also tightens the bond between your application and Stashbox. If you consider this an issue, you can use the [dependency binding](/docs/guides/service-resolution#dependency-binding) API or [your own attributes](/docs/guides/service-resolution#using-your-own-attributes). ::: +### Using your own attributes + + +
+There's an option to extend the container's dependency finding mechanism with your own attributes. + +- **Additional Dependency attributes**: you can use the [`.WithAdditionalDependencyAttribute()`](/docs/configuration/container-configuration#withadditionaldependencyattribute) container configuration option to let the container know that it should watch for additional attributes besides the built-in [`Dependency`](/docs/guides/service-resolution#attributes) attribute upon building up the [resolution tree](/docs/getting-started/glossary#resolution-tree). + +- **Additional DependencyName attributes**: you can use the [`.WithAdditionalDependencyNameAttribute()`](/docs/configuration/container-configuration#withadditionaldependencynameattribute) container configuration option to use additional dependency name indicator attributes besides the built-in [`DependencyName`](/docs/guides/service-resolution#attributes) attribute. + +
+
+ + + + +```cs +class DbBackup : IJob +{ + [CustomDependency("Console")] + public ILogger Logger { get; set; } + + public DbBackup() + { } +} + +var container = new StashboxContainer(options => options + .WithAdditionalDependencyAttribute()); + +container.Register("Console"); +container.Register("File"); + +container.Register(); + +// the container will resolve DbBackup with ConsoleLogger. +IJob job = container.Resolve(); +``` + + + + +```cs +class DbBackup : IJob +{ + public string Name { get; set; } + + public DbBackup([CustomName] string name) + { } +} + +var container = new StashboxContainer(options => options + .WithAdditionalDependencyNameAttribute()); + +container.Register("Backup"); + +// job.Name is "Backup". +IJob job = container.Resolve(); +``` + + + + +
+
+ ## Dependency binding
-The same dependency configuration as attributes is available on the registration configuration API. +The same dependency configuration functionality as attributes, but without attributes. -- **Bind to parameter**: it has the same functionality as the [Dependency attribute](/docs/guides/service-resolution#attributes) on a constructor or method parameter, enabling the [named resolution](/docs/getting-started/glossary#named-resolution). +- **Binding to a parameter**: the same functionality as the [`Dependency`](/docs/guides/service-resolution#attributes) attribute on a constructor or method parameter, enabling [named resolution](/docs/getting-started/glossary#named-resolution). -- **Bind to property/field**: it has the same functionality as the [Dependency attribute](/docs/guides/service-resolution#attributes), enabling the injection of the given property/field. +- **Binding to a property/field**: the same functionality as the [`Dependency`](/docs/guides/service-resolution#attributes) attribute, enabling the injection of the given property/field. :::info There are further dependency binding options [available](/docs/configuration/registration-configuration#dependency-configuration) on the registration configuration API. diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index 72836f0f..d8d6deb1 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -24,7 +24,7 @@ function HomepageHeader() {
-
{'$'} dotnet add package Stashbox --version 5.12.2
+
{'$'} dotnet add package Stashbox --version 5.13.0
diff --git a/test/LifetimeTests.cs b/test/LifetimeTests.cs index ec6bdae3..1b9f52c4 100644 --- a/test/LifetimeTests.cs +++ b/test/LifetimeTests.cs @@ -547,6 +547,17 @@ public void LifetimeTests_AutoLifetime_Remains_Transient(CompilerType compilerTy Assert.NotSame(container.Resolve(), container.Resolve()); } + + [Theory] + [ClassData(typeof(CompilerTypeTestData))] + public void LifetimeTests_AutoLifetime_Without_Dependency(CompilerType compilerType) + { + using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); + + container.Register(c => c.WithAutoLifetime(Lifetimes.Singleton)); + + Assert.NotSame(container.Resolve(), container.Resolve()); + } interface ITest1 { string Name { get; set; } }