有什么方法可以验证 DI 中的生命周期吗?
Is there any way to validate the lifetime in DI?
我正在寻找一些方法来在 .Net Core 或更高版本中强制检查(当然是运行时)依赖注入服务的正确生命周期注册。
假设我有这样一个有状态服务:
public class MyStatefulService
{
private object _state;
}
但是,我错误地注册了错误的生命周期:
services.AddTransient<MyStatefulService>();
所以,我不会收到警报,但实际行为并不是我所期望的:服务是在每次请求时创建的,并且不会保留状态。
我想知道是否有办法加强这种模式。例如,如果我可以用这样的属性装饰 class 就好了:
[Singleton]
public class MyStatefulService
{
private object _state;
}
此时,可能在启动时或在第一次请求时,如果注册不同于 AddSingleton
(及其重载),框架应该抛出异常。
同样,主体可以申请临时服务,不应注册为单身人士。
我想到的唯一解决方案比较幼稚,我不太喜欢:
//line of principle code
public static class MySingletonChecker
{
private static HashSet<Type> _set = new HashSet<Type>();
public static void Validate(Type type)
{
if (_set.Contains(type))
{
throw new Exception();
}
else
{
_set.Add(type);
}
}
}
public class MyStatefulService
{
public MyStatefulService()
{
MySingletonChecker.Validate(this.GetType());
}
private object _state;
}
是否有更好的解决方案、黑客或任何有助于防止错误的方法?
要将 DI 组合移动到 class 的声明中,您可以使用一些自制的标记界面,例如 IRegisterTransientServiceAs<T>
并将其添加到您的 class 中,如下所示:
public class MyStatefulService : IMyStatefulService, IRegisterSingletonServiceAs<IMyStatefulService>
在您的组合中,您拥有自己的扩展方法,该方法遍历应用程序域中的所有加载类型,搜索给定的通用接口并使用给定接口的声明生命周期注册它们。
也许这不是真正的 DI,但正如您已经提到的,大部分服务的生命周期都包含在服务本身的代码中,并且很少发生特定服务将在不同项目中使用不同的情况一生。所以恕我直言,可以将所需的生命周期融入 class 本身,而不是在外部做出决定。
另一种验证生命周期的方法是在 ConfigureServices
子句中检查整个 DI 容器。
IServiceCollection
的每个成员都有一个 LifeTime
属性,它为您提供您正在寻找的信息:https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicedescriptor.lifetime?view=dotnet-plat-ext-5.0#Microsoft_Extensions_DependencyInjection_ServiceDescriptor_Lifetime
如果您想防止您的代码出现白痴问题,您可以向 IServiceCollection
提供一个扩展方法,以完全按照应有的方式注册您的服务。
public static void AddMyStatefulService(this IServiceCollection services)
{
services.AddSingleton<MyStatefulService>();
}
然后在您的服务配置部分,开发人员将键入:
services.AddMyStatefulService();
这是我在评论中指出的方法
让我们有一些空类型:
public class ScopedService {}
public class TransientService {}
让我们有一个派生自空类型之一的真实服务:
public class RealService: ScopedService, IRealService {
//impl
}
还有一个通用的注册方法助手:
static void MyAddScoped<TService, TImplementation>(IServiceCollection services)
where TService : class
where TImplementation : ScopedService, TService
{
services.AddScoped<TService, TImplementation>();
}
让我们使用帮助程序注册我们的真实服务:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
MyAddScoped<IRealService, RealService>(services);
}
RealService 是一个 ScopedService - 将其视为“您如何装饰服务以坚持将其添加为作用域”
假设开发人员将服务更改为瞬态服务:
class RealService: TransientService, IRealService {
现在您从辅助方法中得到一个编译器错误:
The type 'YourApplication.RealService' cannot be used as type parameter 'TImplementation' in the generic type or method 'Startup.MyAddScoped<TService, TImplementation>(IServiceCollection)'. There is no implicit reference conversion from 'WebApplication1.RealService' to 'WebApplication1.ScopedService'.
你的“一个负责注册的人”可以知道开发人员表明该服务不再可注册为 Scoped 并且可以更改它(并且构建将被破坏直到它被更改,这是一个好方法防止意外发布错误代码)
我正在寻找一些方法来在 .Net Core 或更高版本中强制检查(当然是运行时)依赖注入服务的正确生命周期注册。
假设我有这样一个有状态服务:
public class MyStatefulService
{
private object _state;
}
但是,我错误地注册了错误的生命周期:
services.AddTransient<MyStatefulService>();
所以,我不会收到警报,但实际行为并不是我所期望的:服务是在每次请求时创建的,并且不会保留状态。
我想知道是否有办法加强这种模式。例如,如果我可以用这样的属性装饰 class 就好了:
[Singleton]
public class MyStatefulService
{
private object _state;
}
此时,可能在启动时或在第一次请求时,如果注册不同于 AddSingleton
(及其重载),框架应该抛出异常。
同样,主体可以申请临时服务,不应注册为单身人士。
我想到的唯一解决方案比较幼稚,我不太喜欢:
//line of principle code
public static class MySingletonChecker
{
private static HashSet<Type> _set = new HashSet<Type>();
public static void Validate(Type type)
{
if (_set.Contains(type))
{
throw new Exception();
}
else
{
_set.Add(type);
}
}
}
public class MyStatefulService
{
public MyStatefulService()
{
MySingletonChecker.Validate(this.GetType());
}
private object _state;
}
是否有更好的解决方案、黑客或任何有助于防止错误的方法?
要将 DI 组合移动到 class 的声明中,您可以使用一些自制的标记界面,例如 IRegisterTransientServiceAs<T>
并将其添加到您的 class 中,如下所示:
public class MyStatefulService : IMyStatefulService, IRegisterSingletonServiceAs<IMyStatefulService>
在您的组合中,您拥有自己的扩展方法,该方法遍历应用程序域中的所有加载类型,搜索给定的通用接口并使用给定接口的声明生命周期注册它们。
也许这不是真正的 DI,但正如您已经提到的,大部分服务的生命周期都包含在服务本身的代码中,并且很少发生特定服务将在不同项目中使用不同的情况一生。所以恕我直言,可以将所需的生命周期融入 class 本身,而不是在外部做出决定。
另一种验证生命周期的方法是在 ConfigureServices
子句中检查整个 DI 容器。
IServiceCollection
的每个成员都有一个 LifeTime
属性,它为您提供您正在寻找的信息:https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicedescriptor.lifetime?view=dotnet-plat-ext-5.0#Microsoft_Extensions_DependencyInjection_ServiceDescriptor_Lifetime
如果您想防止您的代码出现白痴问题,您可以向 IServiceCollection
提供一个扩展方法,以完全按照应有的方式注册您的服务。
public static void AddMyStatefulService(this IServiceCollection services)
{
services.AddSingleton<MyStatefulService>();
}
然后在您的服务配置部分,开发人员将键入:
services.AddMyStatefulService();
这是我在评论中指出的方法
让我们有一些空类型:
public class ScopedService {}
public class TransientService {}
让我们有一个派生自空类型之一的真实服务:
public class RealService: ScopedService, IRealService {
//impl
}
还有一个通用的注册方法助手:
static void MyAddScoped<TService, TImplementation>(IServiceCollection services)
where TService : class
where TImplementation : ScopedService, TService
{
services.AddScoped<TService, TImplementation>();
}
让我们使用帮助程序注册我们的真实服务:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
MyAddScoped<IRealService, RealService>(services);
}
RealService 是一个 ScopedService - 将其视为“您如何装饰服务以坚持将其添加为作用域”
假设开发人员将服务更改为瞬态服务:
class RealService: TransientService, IRealService {
现在您从辅助方法中得到一个编译器错误:
The type 'YourApplication.RealService' cannot be used as type parameter 'TImplementation' in the generic type or method 'Startup.MyAddScoped<TService, TImplementation>(IServiceCollection)'. There is no implicit reference conversion from 'WebApplication1.RealService' to 'WebApplication1.ScopedService'.
你的“一个负责注册的人”可以知道开发人员表明该服务不再可注册为 Scoped 并且可以更改它(并且构建将被破坏直到它被更改,这是一个好方法防止意外发布错误代码)