首页 > 解决方案 > 通过注解注册 .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
{
}

标签: c#.net-coredependency-injection

解决方案


不,这实际上是设计使然。

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 系统的一个主要缺点是无法静态区分“作用域”服务实现与单例和瞬态服务 - 您可以通过在服务实现上添加标记接口并相应地处理这些来解决此问题。

另一个注意事项:

如果你真的想要,你可以allServiceInterfaceTypes在你的方法中使用上面相同的技术ConfigureServices来枚举所有接口并检测实现每个接口的所有类型,并发现这些类型的任何自定义属性并自动将它们注册到 DI 容器中 - 但我没有t 建议这样做,因为反射比按预期使用 DI 慢 - 并且配置像工厂方法这样的东西会更难。


推荐阅读