SteroidsDI.Core 1.4.2

dotnet add package SteroidsDI.Core --version 1.4.2                
NuGet\Install-Package SteroidsDI.Core -Version 1.4.2                
此命令旨在在 Visual Studio 的包管理器控制台中使用,因为它使用了 NuGet 模块的 Install-Package 版本。
<PackageReference Include="SteroidsDI.Core" Version="1.4.2" />                
对于支持 包引用 的项目,将此 XML 节点复制到项目文件中以引用此包。
paket add SteroidsDI.Core --version 1.4.2                
#r "nuget: SteroidsDI.Core, 1.4.2"                
#r 指令可以在 F# Interactive 和 Polyglot Notebooks 中使用。将此内容复制到交互式工具或脚本的源代码中以引用此包。
// Install SteroidsDI.Core as a Cake Addin
#addin nuget:?package=SteroidsDI.Core&version=1.4.2

// Install SteroidsDI.Core as a Cake Tool
#tool nuget:?package=SteroidsDI.Core&version=1.4.2                

SteroidsDI

<a href="https://www.buymeacoffee.com/sungam3r" target="_blank"><img 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;" ></a>

License

codecov Nuget Nuget

GitHub Release Date GitHub commits since latest release (by date) Size

GitHub contributors Activity Activity Activity

Run unit tests Publish preview to GitHub registry Publish release to Nuget registry CodeQL analysis

每日可用的高级依赖注入。

安装

此存储库提供以下包

下载 Nuget 最新版 描述
SteroidsDI.Core Nuget Nuget 依赖注入原语
SteroidsDI Nuget Nuget 用于 Microsoft.Extensions.DependencyInjection 的高级依赖注入:AddDefer, AddFunc, AddFactory;依赖于 SteroidsDI.Core
SteroidsDI.AspNetCore Nuget Nuget ASP.NET Core 的作用域提供者;依赖于 SteroidsDI.Core

您可以通过 NuGet 安装最新稳定版本

> dotnet add package SteroidsDI
> dotnet add package SteroidsDI.Core
> dotnet add package SteroidsDI.AspNetCore

它是什么?我为什么需要它?

.NET Core 内置了对 依赖注入 的支持。它工作得很好,我们可以使用三种主要生命周期的依赖项:单例、作用域、瞬态。存在一些规则指定了在不同生命周期的对象中传递对象的可能组合。例如,您可能会遇到以下错误消息

验证服务描述符 'ServiceType: I_XXX Lifetime: Singleton ImplementationType: XXX' 时出错:无法从单例 'I_XXX' 消费作用域服务 'YYY'。

错误信息表明你不能将生命周期更短的对象传递给具有长期生命周期的对象的构造函数。这是正确的!问题很清楚。但是如何解决这个问题呢?显然,当使用构造函数依赖注入(.NET Core仅内置支持构造函数依赖注入)时,我们必须遵循以下规则。因此,我们至少有3种选择

  1. 延长注入对象的生命周期。
  2. 缩短注入依赖的对象的生命周期。
  3. 移除这种依赖。
  4. 更改依赖关系的设计以满足规则。

第一种方法并非总是可行。第二种方法更容易实现,尽管这会导致性能下降,因为需要重复创建原本只需创建一次的对象。第三种方式...嗯,你应该明白,这并不会让生活变得容易。因此,我们需要改变设计。这个项目提供了一种解决问题的方法,引入了多个辅助抽象。众所周知

任何编程问题都可以通过引入一个额外的抽象层来解决,除非问题是抽象级别过多。

该项目提供了三个这样的抽象

  1. 大家熟知的Func<T>委托。
  2. Defer<T>/IDefer<T>抽象类似于Lazy<T>,但有一个显著的区别 - Defer<T>/IDefer<T> 不缓存值。
  3. 具有在运行时生成实现类型的命名工厂接口。

所有这些抽象都解决相同的问题,但它们的API设计角度不同。挑战是通过某个中间对象X提供依赖T,其中显式依赖T要么不可能,要么不受欢迎。重要!本包中无实现缓存依赖T。

如上所述,不可能性的例子是对单一-lifetime对象的scoped-lifetime依赖。不欢迎的例子是创建依赖成本高且不是总需要的。

有一点很重要 - 注入依赖和使用依赖不完全相同。实际上,在构造函数注入的情况下,构造函数中的依赖注入(在大多数情况下)会导致将传递值的引用存储在某个字段中。依赖将在稍后调用“父”对象的方法时使用。

Func<T>

此方法是最简单的,提供注入Func<T>而不是T

之前

class MyObject
{
    private IRepository _repo;

    public MyObject(IRepository repo) { _repo = repo; }
    
    public void DoSomething() { _repo.DoMagic(); }
}

之后

class MyObject
{
    private Func<IRepository> _repo;

    public MyObject(Func<IRepository> repo) { _repo = repo; }
    
    public void DoSomething() { _repo().DoMagic(); }
}

如何在DI中配置

public void ConfigureServices(IServiceCollection services)
{
    // First register your IRepository and then call
    services.AddFunc<IRepository>();
}

请注意,您应该为希望注入为Func<T>的每个依赖项T调用AddFunc

IDefer<T>和Defer<T>

此方法建议更明确的API - 注入IDefer<T>Defer<T>而不是T

之前

class MyObject
{
    private IRepository _repo;

    public MyObject(IRepository repo) { _repo = repo; }
    
    public void DoSomething() { _repo.DoMagic(); }
}

之后

class MyObject
{
    private Defer<IRepository> _repo;

    public MyObject(Defer<IRepository> repo) { _repo = repo; }
    
    public void DoSomething() { _repo.Value.DoMagic(); }
}

如何在DI中配置

public void ConfigureServices(IServiceCollection services)
{
    // First register your IRepository and then call
    services.AddDefer();
}

请注意,与AddFunc<T>不同,需要只需调用一次的AddDefer方法。如果需要协变,请使用IDefer<T>接口。

命名工厂

此方法最难实现,但从公共API的角度来看,它和前两种方法一样简单。它假设你声明一个带有一个或多个无参数方法的工厂接口。方法名称并不重要。每个工厂方法都应该返回由DI容器配置的某些依赖类型

public interface IRepositoryFactory
{
    IRepository GetPersonsRepo();
}

并将此工厂注入到你的“父”类型中

class MyObject
{
    private IRepositoryFactory _factory;

    public MyObject(IRepositoryFactory factory) { _factory = factory; }
    
    public void DoSomething() { _factory.GetPersonsRepo().DoMagic(); }
}

如何在DI中配置

public void ConfigureServices(IServiceCollection services)
{
    // First register your IRepository and then call
    services.AddFactory<IRepositoryFactory>();
}

IRepositoryFactory的实现将在运行时生成。

事实上,每个工厂方法可以接受任意类型的一个参数 - 字符串、枚举、自定义类等等。在这种情况下,应指定一个名称绑定。然后,你可以通过传递绑定的名称到工厂方法中解析所需的枚举。如果您想提供一个默认实现,则可以配置默认绑定。默认绑定是在没有指定名称的情况下使用的绑定。用户应显式设置默认绑定,以便能够解析注册名称之外的服务。

public interface IRepositoryFactory
{
    IRepository GetPersonsRepo(string mode);
}

public interface IRepository
{
    void Save(Person person); 
}

public class DemoRepository : IRepository
{
...
}

public class ProductionRepository : IRepository
{
...
}

public class RandomRepository : IRepository
{
...
}

public class DefaultRepository : IRepository
{
...
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IRepository, DemoRepository>()
            .AddTransient<IRepository, ProductionRepository>()
            .AddTransient<IRepository, RandomRepository>()
            .AddFactory<IRepositoryFactory>()
                .For<IRepository>()
                    .Named<DemoRepository>("demo")
                    .Named<ProductionRepository>("prod")
                    .Named<RandomRepository>("rnd")
                    .Default<DefaultRepository>();
}

public class Person
{
    public string Name { get; set; }
}

public class SomeClassWithDependency
{
    private readonly IRepositoryFactory _factory;

    public SomeClassWithDependency(IRepositoryFactory factory)
    {
        _factory = factory;
    }

    private bool SomeInterestingCondition => ...

    public void DoSomething(Person person)
    {
        if (person.Name == "demoUser")
            _factory.GetPersonsRepo("demo").Save(person); // DemoRepository
        else if (person.Name.StartsWith("tester"))
            _factory.GetPersonsRepo("rnd").Save(person); // RandomRepository
        else if (SomeInterestingCondition)
            _factory.GetPersonsRepo("prod").Save(person); // ProductionRepository
        else
            _factory.GetPersonsRepo(person.Name).Save(person); // DefaultRepository
    }
}

在上述示例中,GetPersonsRepo方法将返回对应的IRepository接口实现,该实现已为提供的名称配置。对于所有未注册的名称(包括null),它将返回DefaultRepository

这是如何工作的?

这里一切都很简单。这三种方法都是将依赖项解析委托给合适的 IServiceProvider。什么是合适的?一般来说,在ASP.NET Core应用程序中,人们习惯于使用一个(范围化的)从IHttpContextAccessor获得的提供者 HttpContext.RequestServices。但在一般情况下,可能有许多这样的提供者。此外,依赖项消耗代码并不知道它们的存在。这段代码可能是一般的库,与特定于应用程序的环境不是很紧密耦合。因此,引入了抽象来获取合适的 IServiceProvider。是的,又有一个抽象!

本项目提供了两个内置提供者

  1. AspNetCoreHttpScopeProvider为ASP.NET Core应用程序。
  2. GenericScopeProvider为通用类库。

如何在DI中配置

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpScope();
    services.AddGenericScope<SomeClass>();
}

当然,您始终可以编写自己的提供者

public class MyScopeProvider : IScopeProvider
{
    public IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider) => rootProvider.ReturnSomeMagic();
}

并在DI中提供其注册

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyScope(this IServiceCollection services)
    {
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IScopeProvider, MyScopeProvider>());
        return services;
    }
}

高级行为

您可以通过ServiceProviderAdvancedOptions自定义AddFunc/AddDefer/AddFactory API的行为:只需使用来自Microsoft.Extensions.Options/Microsoft.Extensions.Options.ConfigurationExtensions软件包的标准扩展方法。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ServiceProviderAdvancedOptions>(options => options.AllowRootProviderResolve = true)
    services.Configure<ServiceProviderAdvancedOptions>(Configuration.GetSection("Steroids"));
}

示例

您可以在示例项目中看到如何使用所有上述API。

常见问题解答

。等等,Microsoft.Extensions.DependencyInjection不支持これ出厂默认设置吗?

。不幸的是,没有。我自己也希望能够使用现有的功能而不是编写自己的包。

<br/>

。您提供的是不是ServiceLocator?我听说ServiceLocator是反模式。

。是的,ServiceLocator是一个已知反模式。与所提出的解决方案的基本区别在于,ServiceLocator允许您在运行时解析任何依赖项,而Func、Defer和Named Factory专为解析编译时指定的已知依赖项而设计。因此,主要的区别是,类的所有依赖项都明确声明,并将注入到其中。类本身不会在其实现中秘密“拉”这些依赖项。这被称为显式依赖项原则

<br/>

。这是依赖注入的新方法吗?

。实际上不是。这种方法可以追溯到许多年前的文章和博客中,例如这里

<br/>

。我应该选择Func<>或者[I]Defer<>?

。它们在进行到底层的工作方式相同。不同之处在于您将要使用这些API的上下文。

主要有两个区别

  1. Func<> 的优点是注入 Func<> 的代码不需要任何新的依赖项,它是一个众所周知 的 .NET 委托类型。相反,[I]Defer<> 需要引用 SteroidsDI.Core 包。

  2. 对于您要注入为 Func<T> 的每个依赖项 T,应调用 AddFunc。相反,AddDefer 方法只需调用一次。

<br/>

:如果我想创建自己的作用域来使用,即不仅消耗它,还提供它怎么办?

。首先,您需要以某种方式获取根 IServiceProvider 的实例。然后,通过调用它的 CreateScope() 方法创建作用域,并将其设置在 GenericScope 中。

var rootProvider = ...;
using var scope = rootProvider.CreateScope();
GenericScope<SomeClass>.CurrentScope = scope;
...
... Some code here that works with scopes.
... All registered Func<T>, [I]Defer<T> and
... factories use created scope.
...
GenericScope<T>.CurrentScope = null;

或者,您可以使用更简单的使用 Scoped<T> 结构的方法。

IScopeFactory scopeFactory = ...; // can be obtained from DI, see AddMicrosoftScopeFactory extension method
using (new Scoped<SomeClass>(scopeFactory))
or
await using (new Scoped<SomeClass>(scopeFactory)) // Scoped class supports IAsyncDisposable as well
{
...
... Some code here that works with scopes.
... All registered Func<T>, [I]Defer<T> and
... factories use created scope.
...
} 

还可以参考 ScopedTestBaseScopedTestDerived,以获取更多详细信息。该示例显示您如何将作用域支持添加到所有单元测试中。

基准测试

结果可在 此处 获得。

产品 兼容的和额外的计算目标框架版本。
.NET net5.0 已计算。 net5.0-windows 已计算。 net6.0 已计算。 net6.0-android 已计算。 net6.0-ios 已计算。 net6.0-maccatalyst 已计算。 net6.0-macos 已计算。 net6.0-tvos 已计算。 net6.0-windows 已计算。 net7.0 已计算。 net7.0-android 已计算。 net7.0-ios 已计算。 net7.0-maccatalyst 已计算。 net7.0-macos 已计算。 net7.0-tvos 已计算。 net7.0-windows 已计算。 net8.0 已计算。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。
.NET Core netcoreapp2.0 已计算。 netcoreapp2.1 已计算。 netcoreapp2.2 已计算。 netcoreapp3.0 已计算。 netcoreapp3.1 已计算。
.NET Standard netstandard2.0 兼容。 netstandard2.1 兼容。
.NET Framework net461 已计算。 net462 已计算。 net463 已计算。 net47 已计算。 net471 已计算。 net472 已计算。 net48 已计算。 net481 已计算。
MonoAndroid monoandroid 已计算。
MonoMac monomac 已计算。
MonoTouch monotouch 已计算。
Tizen tizen40 已计算。 tizen60 已计算。
Xamarin.iOS xamarinios 已计算。
Xamarin.Mac xamarinmac 已计算。
Xamarin.TVOS xamarintvos 已计算。
Xamarin.WatchOS xamarinwatchos 已计算。
兼容的目标框架
包括的目标框架(在包中)
有关更多信息,请参阅目标框架.NET Standard

NuGet 包 (2)

显示依赖 SteroidsDI.Core 的前 2 个 NuGet 包

下载
SteroidsDI

Microsoft.Extensions.DependencyInjection 的高级依赖注入:AddDefer、AddFunc、AddFactory。

SteroidsDI.AspNetCore

ASP.NET Core 的作用域提供者

GitHub 仓库

此包未被任何流行的 GitHub 仓库使用。

版本 下载 最后更新
1.4.2 2,465 3/18/2024
1.4.1 2,118 1/20/2024
1.4.0 637 1/4/2024
1.3.0 1,014 11/18/2023
1.2.0 10,637 12/29/2022
1.1.1 3,219 12/1/2022
1.1.0 9,901 5/2/2022
1.0.3 1,696 4/15/2022
1.0.2 2,082 3/31/2022
1.0.1 8,415 10/16/2021
1.0.0 22,532 2/26/2021
0.0.7 4,608 11/24/2020
0.0.6 1,275 11/24/2020
0.0.5 2,288 10/26/2020
0.0.4 19,482 6/25/2020
0.0.3 1,499 3/10/2020
0.0.2 1,378 3/9/2020
0.0.1 1,447 3/8/2020