c# - 通过注解注册 .NET Core 单例服务
问题描述
有没有办法通过注释将单例服务自动连接(自动注册)到 C# DI 容器(Microsoft.Extensions.DependencyInjection) ?
例如,Angular 中的 @Injectable() 注释中的 ProvidedIn 选项、InversifyJS (Node.js)中的 @injectable() 注释、Spring (Java)中的自动装配或Symfony 框架 (PHP)中的自动装配?
请参阅下面的角度示例:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UserService {
}
此时我必须以这种方式手动将每个单例服务添加到 ServiceCollection (有时会忘记这样做):
internal static ServiceProvider SetupDi()
{
return new ServiceCollection() // Microsoft.Extensions.DependencyInjection.ServiceCollection.ServiceCollection()
.AddDbContext<DbContext>()
.AddSingleton<ServiceA>()
.AddSingleton<ServiceB>();
}
所需的等效解决方案将是这样的:
internal static ServiceProvider SetupDi()
{
return new ServiceCollection() // Microsoft.Extensions.DependencyInjection.ServiceCollection.ServiceCollection()
.AddDbContext<DbContext>();
}
[Injectable()]
public class ServiceA
{
}
[Injectable()]
public class ServiceB
{
}
解决方案
不,这实际上是设计使然。
DI 的全部意义在于,您的程序的配置方式并不令人意外ConfigureServices
:您在其中配置的所有内容(在您的情况下SetupDi
为 )正是您在运行时获得的内容。通过使用属性来配置 DI,这将引入“非本地影响”,并且更难追踪由错误属性引起的不正确或错误配置的依赖项引入的错误。
(在这个程度上,我不同意 Angular 的设计——但那是题外话)。
(我也觉得 .NET Core 的 DI 系统也很不完善——DI 注入扩展方法背后隐藏了太多必要的细节,需要使用 ILSpy 或 Reflector 才能窥视)。
作为一种解决方法,您可以在应用程序启动时“测试”您的 DI 服务,以确保通过反映IService
项目中的所有内容并尝试实例化实现来配置所有内容。
这是我在 ASP.NET 和 ASP.NET Core 项目中使用的代码,用于验证 DI 是否已完全配置:
internal static void TestAllServices( IServiceProvider sp )
{
Assembly[] mySolutionAssemblies = new[]
{
typeof(FromAssemblyX.Foobar).Assemby,
typeof(FromAssemblyY.Foobar).Assemby,
typeof(FromAssemblyZ.Foobar).Assemby,
};
List<Type> allServiceInterfaceTypes = mySolutionAssemblies
.SelectMany( ass => ass.GetTypes() )
.Where( t => t.IsInterface && t.IsPublic )
.Where( /* Filter out interfaces you don't want to verify here */ )
.ToList();
foreach( Type serviceInterfaceType in serviceInterfaceTypes )
{
try
{
Object implementation = sp.GetRequiredService( serviceInterfaceType );
if( implementation is IDisposable disp ) disp.Dispose();
}
catch( Exception ex )
{
// Log an error or throw or set a breakpoint here
}
}
}
笔记:
- 不要将您的“真实”
IServiceProvider
传入TestAllServices
- 而是创建一个单独的IServiceProvider
实例,因为此方法将处理任何实现IDisposable
,即使它们是单例。 - 我建议包含此代码
#if DEBUG
以确保它不会投入生产。 - .NET Core 的默认 DI 系统的一个主要缺点是无法静态区分“作用域”服务实现与单例和瞬态服务 - 您可以通过在服务实现上添加标记接口并相应地处理这些来解决此问题。
- 虽然公平地说,ASP.NET Core可以自行验证您的作用域依赖项- 但您需要使用
.AddControllersAsServices()
.
- 虽然公平地说,ASP.NET Core可以自行验证您的作用域依赖项- 但您需要使用
另一个注意事项:
如果你真的想要,你可以allServiceInterfaceTypes
在你的方法中使用上面相同的技术ConfigureServices
来枚举所有接口并检测实现每个接口的所有类型,并发现这些类型的任何自定义属性并自动将它们注册到 DI 容器中 - 但我没有t 建议这样做,因为反射比按预期使用 DI 慢 - 并且配置像工厂方法这样的东西会更难。
推荐阅读
- python - 将两个不同形状的numpy数组相乘时是否有自动广播的功能?
- swift - 以编程方式向左或向右移动空间 OSX
- python - 我有一个 Excel 表,其中包含一些页眉和页脚以及数据,现在我想在其间编辑该数据框的一列
- python - 如何用python在一个系列中逐月交换?
- hive - 从 Hive 表中检索 JSON 原始文件数据
- python - matplotlib 添加到 tkinter 中的 panedwindow
- cakephp - 在 CakePHP 3 中调用布尔错误时的成员函数 build()
- sql-server - SQL Server 将列中的所有数字加 1
- python - UnicodeEncodeError:“charmap”编解码器无法在位置 4297 编码字符“\u221e”:字符映射到
- java - 在 Java 中使用 Streams 和过滤器并与可变参数匹配