SteroidsDI.Core 1.4.2
dotnet add package SteroidsDI.Core --version 1.4.2
NuGet\Install-Package SteroidsDI.Core -Version 1.4.2
<PackageReference Include="SteroidsDI.Core" Version="1.4.2" />
paket add SteroidsDI.Core --version 1.4.2
#r "nuget: SteroidsDI.Core, 1.4.2"
// 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>
每日可用的高级依赖注入。
安装
此存储库提供以下包
您可以通过 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种选择
- 延长注入对象的生命周期。
- 缩短注入依赖的对象的生命周期。
- 移除这种依赖。
- 更改依赖关系的设计以满足规则。
第一种方法并非总是可行。第二种方法更容易实现,尽管这会导致性能下降,因为需要重复创建原本只需创建一次的对象。第三种方式...嗯,你应该明白,这并不会让生活变得容易。因此,我们需要改变设计。这个项目提供了一种解决问题的方法,引入了多个辅助抽象。众所周知
任何编程问题都可以通过引入一个额外的抽象层来解决,除非问题是抽象级别过多。
该项目提供了三个这样的抽象
- 大家熟知的
Func<T>
委托。 Defer<T>
/IDefer<T>
抽象类似于Lazy<T>
,但有一个显著的区别 -Defer<T>
/IDefer<T>
不缓存值。- 具有在运行时生成实现类型的命名工厂接口。
所有这些抽象都解决相同的问题,但它们的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
。是的,又有一个抽象!
本项目提供了两个内置提供者
AspNetCoreHttpScopeProvider
为ASP.NET Core应用程序。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已知
依赖项而设计。因此,主要的区别是,类的所有依赖项都明确声明,并将注入到其中。类本身不会在其实现中秘密“拉”这些依赖项。这被称为显式依赖项原则。
<br/>
问。这是依赖注入的新方法吗?
答。实际上不是。这种方法可以追溯到许多年前的文章和博客中,例如这里。
<br/>
问。我应该选择Func<>或者[I]Defer<>?
答。它们在进行到底层的工作方式相同。不同之处在于您将要使用这些API的上下文。
主要有两个区别
Func<>
的优点是注入Func<>
的代码不需要任何新的依赖项,它是一个众所周知 的 .NET 委托类型。相反,[I]Defer<>
需要引用SteroidsDI.Core
包。对于您要注入为
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.
...
}
还可以参考 ScopedTestBase 和 ScopedTestDerived,以获取更多详细信息。该示例显示您如何将作用域支持添加到所有单元测试中。
基准测试
结果可在 此处 获得。
产品 | 版本 兼容的和额外的计算目标框架版本。 |
---|---|
.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 已计算。 |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
-
.NETStandard 2.1
- 无依赖项。
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 |