{"id":17062102,"url":"https://github.com/sungam3r/steroidsdi","last_synced_at":"2025-04-12T18:31:01.317Z","repository":{"id":36969303,"uuid":"245805226","full_name":"sungam3r/SteroidsDI","owner":"sungam3r","description":"Advanced Dependency Injection to use every day.","archived":false,"fork":false,"pushed_at":"2025-01-20T15:04:37.000Z","size":272,"stargazers_count":21,"open_issues_count":2,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-26T12:44:28.916Z","etag":null,"topics":["defer","dependency-injection","factory","steroids"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sungam3r.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["sungam3r"],"custom":["https://www.buymeacoffee.com/sungam3r","https://paypal.me/sungam3r"]}},"created_at":"2020-03-08T11:53:04.000Z","updated_at":"2025-01-19T17:20:08.000Z","dependencies_parsed_at":"2023-12-14T17:35:44.422Z","dependency_job_id":"dd5b0f73-dc41-48df-8f41-859d99ccb474","html_url":"https://github.com/sungam3r/SteroidsDI","commit_stats":{"total_commits":148,"total_committers":5,"mean_commits":29.6,"dds":0.5067567567567568,"last_synced_commit":"a648d883bfeb6e38d8654462b49e4cf8530686c7"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sungam3r%2FSteroidsDI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sungam3r%2FSteroidsDI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sungam3r%2FSteroidsDI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sungam3r%2FSteroidsDI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sungam3r","download_url":"https://codeload.github.com/sungam3r/SteroidsDI/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248610687,"owners_count":21132984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["defer","dependency-injection","factory","steroids"],"created_at":"2024-10-14T10:49:13.046Z","updated_at":"2025-04-12T18:31:01.294Z","avatar_url":"https://github.com/sungam3r.png","language":"C#","funding_links":["https://github.com/sponsors/sungam3r","https://www.buymeacoffee.com/sungam3r","https://paypal.me/sungam3r"],"categories":[],"sub_categories":[],"readme":"# SteroidsDI\n\n\u003ca href=\"https://www.buymeacoffee.com/sungam3r\" target=\"_blank\"\u003e\u003cimg src=\"https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png\" alt=\"Buy Me A Coffee\" style=\"height: auto !important;width: auto !important;\" \u003e\u003c/a\u003e\n\n![License](https://img.shields.io/github/license/sungam3r/SteroidsDI)\n\n[![codecov](https://codecov.io/gh/sungam3r/SteroidsDI/graph/badge.svg?token=0ZRHIUEQM4)](https://codecov.io/gh/sungam3r/SteroidsDI)\n[![Nuget](https://img.shields.io/nuget/dt/SteroidsDI.Core)](https://www.nuget.org/packages/SteroidsDI.Core)\n[![Nuget](https://img.shields.io/nuget/v/SteroidsDI.Core)](https://www.nuget.org/packages/SteroidsDI.Core)\n\n[![GitHub Release Date](https://img.shields.io/github/release-date/sungam3r/SteroidsDI?label=released)](https://github.com/sungam3r/SteroidsDI/releases)\n[![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/sungam3r/SteroidsDI/latest?label=new+commits)](https://github.com/sungam3r/SteroidsDI/commits/master)\n![Size](https://img.shields.io/github/repo-size/sungam3r/SteroidsDI)\n\n[![GitHub contributors](https://img.shields.io/github/contributors/sungam3r/SteroidsDI)](https://github.com/sungam3r/SteroidsDI/graphs/contributors)\n![Activity](https://img.shields.io/github/commit-activity/w/sungam3r/SteroidsDI)\n![Activity](https://img.shields.io/github/commit-activity/m/sungam3r/SteroidsDI)\n![Activity](https://img.shields.io/github/commit-activity/y/sungam3r/SteroidsDI)\n\n[![Run unit tests](https://github.com/sungam3r/SteroidsDI/actions/workflows/test.yml/badge.svg)](https://github.com/sungam3r/SteroidsDI/actions/workflows/test.yml)\n[![Publish preview to GitHub registry](https://github.com/sungam3r/SteroidsDI/actions/workflows/publish-preview.yml/badge.svg)](https://github.com/sungam3r/SteroidsDI/actions/workflows/publish-preview.yml)\n[![Publish release to Nuget registry](https://github.com/sungam3r/SteroidsDI/actions/workflows/publish-release.yml/badge.svg)](https://github.com/sungam3r/SteroidsDI/actions/workflows/publish-release.yml)\n[![CodeQL analysis](https://github.com/sungam3r/SteroidsDI/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/sungam3r/SteroidsDI/actions/workflows/codeql-analysis.yml)\n\nAdvanced Dependency Injection to use every day.\n\n## Installation\n\nThis repository provides the following packages:\n\n| Package | Downloads | Nuget Latest | Description |\n|---------|-----------|--------------|-------------|\n| SteroidsDI.Core | [![Nuget](https://img.shields.io/nuget/dt/SteroidsDI.Core)](https://www.nuget.org/packages/SteroidsDI.Core) | [![Nuget](https://img.shields.io/nuget/v/SteroidsDI.Core)](https://www.nuget.org/packages/SteroidsDI.Core) | Dependency Injection primitives |\n| SteroidsDI | [![Nuget](https://img.shields.io/nuget/dt/SteroidsDI)](https://www.nuget.org/packages/SteroidsDI) | [![Nuget](https://img.shields.io/nuget/v/SteroidsDI)](https://www.nuget.org/packages/SteroidsDI) | Advanced Dependency Injection for Microsoft.Extensions.DependencyInjection: AddDefer, AddFunc, AddFactory; depends on SteroidsDI.Core |\n| SteroidsDI.AspNetCore | [![Nuget](https://img.shields.io/nuget/dt/SteroidsDI.AspNetCore)](https://www.nuget.org/packages/SteroidsDI.AspNetCore) | [![Nuget](https://img.shields.io/nuget/v/SteroidsDI.AspNetCore)](https://www.nuget.org/packages/SteroidsDI.AspNetCore) | Scope Provider for ASP.NET Core; depends on SteroidsDI.Core |\n\nYou can install the latest stable version via NuGet:\n```\n\u003e dotnet add package SteroidsDI\n\u003e dotnet add package SteroidsDI.Core\n\u003e dotnet add package SteroidsDI.AspNetCore\n```\n\n## What is it ? Why do I need it ?\n\n.NET Core has built-in support for [Dependency Injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection).\nIt works and works quite well. We can use the dependencies of the three main lifetimes: singleton, scoped, transient. There are rules that\nspecify possible combinations of passing objects with one lifetime in objects with another lifetime. For example, you may encounter such\nan error message:\n\u003e Error while validating the service descriptor 'ServiceType: I_XXX Lifetime: Singleton ImplementationType: XXX':\n\u003e Cannot consume scoped service 'YYY' from singleton 'I_XXX'.\n\nThe error says that you cannot pass an object with a shorter lifetime to the constructor of a long-living object. Well that's right!\nThe problem is clear. But how to solve it? Obviously, when using **constructor dependency injection** (.NET Core has built-in support \nonly for constructor DI), we must follow these rules. So we have at least 3 options:\n\n1. Lengthen lifetime for injected object.\n1. Shorten lifetime for an object that injects a dependency.\n1. Remove such a dependency.\n1. Change the design of dependencies so as to satisfy the rules.\n\nThe first method is far from always possible. The second method is much easier to implement, although this will lead to a decrease\nin performance due to the repeated creation of objects that were previously created once. The third way... well, you understand,\nlife will not become easier. So it remains to somehow change the design. This project just offers such a way to solve the problem,\nintroducing a number of auxiliary abstractions. As many already know\n\n\u003e Any programming problem can be solved by introducing an additional level of abstraction with the exception of the problem of an\n\u003e excessive number of abstractions.\n\nThe project provides three such abstractions:\n1. Well known `Func\u003cT\u003e` delegate.\n1. `Defer\u003cT\u003e`/`IDefer\u003cT\u003e` abstractions which look like `Lazy\u003cT\u003e` but have a significant difference - `Defer\u003cT\u003e`/`IDefer\u003cT\u003e`\n**do not cache the value**.\n1. A named factory interface, when implementation type is generated at runtime.\n\nAll these abstractions **solve the same problem**, approaching the design of their API from different angles. The challenge is to\nprovide a dependency T through some intermediary object X where an explicit dependency on T is either not possible or not desirable.\n**Important! No implementation in this package caches dependency T.**\n\nAs mentioned above an example of impossibility is a dependency on a scoped lifetime in an object with a singleton lifetime. And an\nexample of non-desirability is creating dependency is expensive and not always required.\n\n**There is one important point to make - injecting dependency is not the same as using dependency.** In fact, in the case of\nconstructor injection, the injection of the dependency in the constructor leads (in most cases) to storing a reference to the\npassed value in the some field. The dependency will be used later when calling the methods of \"parent\" object.\n\n## Func\\\u003cT\u003e\n\nThis method is the easiest and offers to inject `Func\u003cT\u003e` instead of `T`:\n\nBefore:\n\n```csharp\nclass MyObject\n{\n    private IRepository _repo;\n\n    public MyObject(IRepository repo) { _repo = repo; }\n    \n    public void DoSomething() { _repo.DoMagic(); }\n}\n```\n\nAfter:\n\n```csharp\nclass MyObject\n{\n    private Func\u003cIRepository\u003e _repo;\n\n    public MyObject(Func\u003cIRepository\u003e repo) { _repo = repo; }\n    \n    public void DoSomething() { _repo().DoMagic(); }\n}\n```\n\nHow to configure in DI:\n\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n    // First register your IRepository and then call\n    services.AddFunc\u003cIRepository\u003e();\n}\n```\n\nNote that you should call `AddFunc` for each dependency `T` which you want to inject as `Func\u003cT\u003e`.\n\n## IDefer\\\u003cT\u003e and Defer\\\u003cT\u003e\n\nThis method suggests more explicit API - inject `IDefer\u003cT\u003e` or `Defer\u003cT\u003e` instead of `T`:\n\nBefore:\n\n```csharp\nclass MyObject\n{\n    private IRepository _repo;\n\n    public MyObject(IRepository repo) { _repo = repo; }\n    \n    public void DoSomething() { _repo.DoMagic(); }\n}\n```\n\nAfter:\n\n```csharp\nclass MyObject\n{\n    private Defer\u003cIRepository\u003e _repo;\n\n    public MyObject(Defer\u003cIRepository\u003e repo) { _repo = repo; }\n    \n    public void DoSomething() { _repo.Value.DoMagic(); }\n}\n```\n\nHow to configure in DI:\n\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n    // First register your IRepository and then call\n    services.AddDefer();\n}\n```\n\nNote that unlike `AddFunc\u003cT\u003e`, the `AddDefer` method needs to be called only once. Use `IDefer\u003cT\u003e` interface if you\nneed covariance. \n\n## Named factory\n\nThis method is the most difficult to implement, but from the public API point of view it is just as simple as previous two.\nIt assumes that you declare a factory interface with one or more methods without parameters. Method name does not matter.\nEach factory method should return some dependency type configured in DI container:\n\n```csharp\npublic interface IRepositoryFactory\n{\n    IRepository GetPersonsRepo();\n}\n```\n\nAnd inject this factory into your \"parent\" type:\n\n```csharp\nclass MyObject\n{\n    private IRepositoryFactory _factory;\n\n    public MyObject(IRepositoryFactory factory) { _factory = factory; }\n    \n    public void DoSomething() { _factory.GetPersonsRepo().DoMagic(); }\n}\n```\n\nHow to configure in DI:\n\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n    // First register your IRepository and then call\n    services.AddFactory\u003cIRepositoryFactory\u003e();\n}\n```\n\nImplementation for `IRepositoryFactory` will be generated at runtime.\n\nIn fact, each factory method can take one parameter of an arbitrary type - string, enum, custom class, whatever.\nIn this case, a _named binding_ should be specified. Then you may resolve required services passing the name of\nthe binding into factory methods. If you want to provide a default implementation then you may configure _default\nbinding_. Default binding is such a binding used in the absence of a named one. A user should set default binding\nexplicitly to be able to resolve services for unregistered names.\n\n```csharp\npublic interface IRepositoryFactory\n{\n    IRepository GetPersonsRepo(string mode);\n}\n\npublic interface IRepository\n{\n    void Save(Person person); \n}\n\npublic class DemoRepository : IRepository\n{\n...\n}\n\npublic class ProductionRepository : IRepository\n{\n...\n}\n\npublic class RandomRepository : IRepository\n{\n...\n}\n\npublic class DefaultRepository : IRepository\n{\n...\n}\n\npublic void ConfigureServices(IServiceCollection services)\n{\n    services.AddTransient\u003cIRepository, DemoRepository\u003e()\n            .AddTransient\u003cIRepository, ProductionRepository\u003e()\n            .AddTransient\u003cIRepository, RandomRepository\u003e()\n            .AddFactory\u003cIRepositoryFactory\u003e()\n                .For\u003cIRepository\u003e()\n                    .Named\u003cDemoRepository\u003e(\"demo\")\n                    .Named\u003cProductionRepository\u003e(\"prod\")\n                    .Named\u003cRandomRepository\u003e(\"rnd\")\n                    .Default\u003cDefaultRepository\u003e();\n}\n\npublic class Person\n{\n    public string Name { get; set; }\n}\n\npublic class SomeClassWithDependency\n{\n    private readonly IRepositoryFactory _factory;\n\n    public SomeClassWithDependency(IRepositoryFactory factory)\n    {\n        _factory = factory;\n    }\n\n    private bool SomeInterestingCondition =\u003e ...\n\n    public void DoSomething(Person person)\n    {\n        if (person.Name == \"demoUser\")\n            _factory.GetPersonsRepo(\"demo\").Save(person); // DemoRepository\n        else if (person.Name.StartsWith(\"tester\"))\n            _factory.GetPersonsRepo(\"rnd\").Save(person); // RandomRepository\n        else if (SomeInterestingCondition)\n            _factory.GetPersonsRepo(\"prod\").Save(person); // ProductionRepository\n        else\n            _factory.GetPersonsRepo(person.Name).Save(person); // DefaultRepository\n    }\n}\n```\n\nIn the example above, the `GetPersonsRepo` method will return the corresponding implementation of the `IRepository`\ninterface, configured for the provided name. For all unregistered names (including null) it will return `DefaultRepository`.\n\n## How it works?\n\n[Everything is simple here](src/SteroidsDI/Resolver.cs). All three methods come down to delegating dependency resolution to\nthe _appropriate_ `IServiceProvider`. What does _appropriate_ mean? As a rule, in a ASP.NET Core application, everyone\nis used to working with one (scoped) provider obtained from `IHttpContextAccessor` - `HttpContext.RequestServices`.\nBut in the general case, there can be many such providers. In addition, dependency-consuming code is not aware of their\nexistence. This code may be a general purpose library no tightly coupled with application specific environment. Therefore\nabstraction for obtaining the _appropriate_ `IServiceProvider` is [introduced](src/SteroidsDI.Core/IScopeProvider.cs). Yes,\none more abstraction again!\n\nThis project provides two built-in providers:\n1. [`AspNetCoreHttpScopeProvider`](src/SteroidsDI.AspNetCore/AspNetCoreHttpScopeProvider.cs) for ASP.NET Core apps.\n1. [`GenericScopeProvider\u003cT\u003e`](src/SteroidsDI/GenericScopeProvider.cs) for general purpose libraries.\n\nHow to configure in DI:\n\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n    services.AddHttpScope();\n    services.AddGenericScope\u003cSomeClass\u003e();\n}\n```\n\nAnd of course you can always write your own provider:\n\n```csharp\npublic class MyScopeProvider : IScopeProvider\n{\n    public IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider) =\u003e rootProvider.ReturnSomeMagic();\n}\n```\n\nAnd provide its registration in DI:\n\n```csharp\npublic static class ServiceCollectionExtensions\n{\n    public static IServiceCollection AddMyScope(this IServiceCollection services)\n    {\n        services.TryAddEnumerable(ServiceDescriptor.Singleton\u003cIScopeProvider, MyScopeProvider\u003e());\n        return services;\n    }\n}\n```\n\n## Advanced behavior\n\nYou can customize the behavior of `AddFunc`/`AddDefer`/`AddFactory` APIs via [ServiceProviderAdvancedOptions](src/SteroidsDI/ServiceProviderAdvancedOptions.cs):\nJust use standard extension methods from `Microsoft.Extensions.Options/Microsoft.Extensions.Options.ConfigurationExtensions` packages.\n\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n    services.Configure\u003cServiceProviderAdvancedOptions\u003e(options =\u003e options.AllowRootProviderResolve = true)\n    services.Configure\u003cServiceProviderAdvancedOptions\u003e(Configuration.GetSection(\"Steroids\"));\n}\n```\n\n## Examples\n\nYou can see how to use all the aforementioned APIs in the [example project](https://github.com/sungam3r/SteroidsDI/tree/master/src/Example).\n\n## FAQ\n\n**Q**. Wait a moment. Doesn't `Microsoft.Extensions.DependencyInjection` have support for this out of the box?\n\n**A**. Unfortunately no. I myself would rather be able to use the existing feature than to write my own package.\n\n\u003cbr/\u003e\n\n**Q**. Isn't what you offer is a **ServiceLocator**? I heard that the ServiceLocator is anti-pattern.\n\n**A**. Yes, ServiceLocator is a [known antipattern](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/).\nThe fundamental difference with the proposed solutions is that ServiceLocator allows you to resolve\n**any** dependency in runtime while `Func\u003cT\u003e`, `Defer\u003cT\u003e` and Named Factory are designed to resolve\nonly **known** dependencies specified at the compile-time. Thus, the principal difference is that all\nthe dependences of the class are declared explicitly and are injected into it. The class itself **does\nnot pull** these dependencies secretly within its implementation. This is so called [Explicit Dependencies Principle](https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#explicit-dependencies).\n\n\u003cbr/\u003e\n\n**Q**. Is this some kind of new dependency injection approach?\n\n**A**. Actually not. A description of this approach can be found in articles/blogs many years ago, for example\n[here](https://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/).\n\n\u003cbr/\u003e\n\n**Q**. What should I prefer - `Func\u003c\u003e` or `[I]Defer\u003c\u003e`?\n\n**A**. The main thing is they all work equally under the hood. The difference is in which context you are\ngoing to use these APIs.\n\nThere are two main differences:\n\n1) The advantage of `Func\u003c\u003e` is that the code in which you inject `Func\u003c\u003e` does not require any new\ndependency, it is well-known .NET delegate type. On the contrary `[I]Defer\u003c\u003e` requires a reference to\n`SteroidsDI.Core` package.\n\n2) You should call `AddFunc` for each dependency `T` which you want to inject as `Func\u003cT\u003e`. On the\ncontrary the `AddDefer` method needs to be called only once.\n\n\u003cbr/\u003e\n\n**Q**. What if I want to create my own scope to work with, i.e. not only consume it but also provide?\n\n**A**. First you should somehow get an instance of root `IServiceProvider`.\nThen create scope by calling `CreateScope()` method on it and set it into `GenericScope`:\n\n```csharp\nvar rootProvider = ...;\nusing var scope = rootProvider.CreateScope();\nGenericScope\u003cSomeClass\u003e.CurrentScope = scope;\n...\n... Some code here that works with scopes.\n... All registered Func\u003cT\u003e, [I]Defer\u003cT\u003e and\n... factories use created scope.\n...\nGenericScope\u003cT\u003e.CurrentScope = null;\n```\n\nOr you can use a bit simpler approach with [`Scoped\u003cT\u003e`](src/SteroidsDI.Core/Scoped.cs) struct.\n\n```csharp\nIScopeFactory scopeFactory = ...; // can be obtained from DI, see AddMicrosoftScopeFactory extension method\nusing (new Scoped\u003cSomeClass\u003e(scopeFactory))\nor\nawait using (new Scoped\u003cSomeClass\u003e(scopeFactory)) // Scoped class supports IAsyncDisposable as well\n{\n...\n... Some code here that works with scopes.\n... All registered Func\u003cT\u003e, [I]Defer\u003cT\u003e and\n... factories use created scope.\n...\n} \n```\n\nAlso see [ScopedTestBase](src/SteroidsDI.Tests/Cases/ScopedTestBase.cs) and [ScopedTestDerived](src/SteroidsDI.Tests/Cases/ScopedTestDerived.cs)\nfor more info. This example shows how you can add scope support to all unit tests.\n\n## Benchmarks\n\nThe results are available [here](https://sungam3r.github.io/SteroidsDI/dev/bench/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsungam3r%2Fsteroidsdi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsungam3r%2Fsteroidsdi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsungam3r%2Fsteroidsdi/lists"}