You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让ASP.NET Core中ServiceCollectionExtensions的AddLogger方法更通用

优化日志注入的泛型性,降低DI配置的维护成本

我有一个包含AddLogger方法的ServiceCollectionExtensions类,目前运行正常,但想让它更具泛型性。比如我有类A依赖类B,类B依赖ILogger<B>,现在通过InitServices配置DI依赖项是可行的,但这种方式很脆弱——如果类A的实现变更,开始依赖其他服务,InitServices也得跟着更新。

现有代码

TestBase类

public abstract class IntegrationTestBase<TService, TImplementation>
where TService : class
where TImplementation : class, TService
{
    protected TService Sut(Action<IServiceCollection>? configureService = default)
    {
        var services = new ServiceCollection();
        services.AddLogger<TImplementation>();
        services.AddSut<TService, TImplementation>();

        configureService?.Invoke(services);

        return services.BuildServiceProvider().GetRequiredService<TService>();
    }
}

ServiceCollectionExtensions类

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddLogger<TCategoryName>(this IServiceCollection services) where TCategoryName : class
    {
        services.AddSingleton(Mock.Of<ILogger<TCategoryName>>());
        return services;
    } 
}

测试类

[TestMethod]
public void SessionID()
{
    // Arrange
    var swa = Sut(services => InitServices(services));
}

private static void InitServices(IServiceCollection services, bool refreshTokenGrantEnabled = false, bool passwordGrantEnabled = false, bool stsEnabled = true)
{
     services
         .AddSingleton<B>()
         .AddLogger<B>();
}

优化方案

1. 注册通用Mock Logger,自动适配所有类型

不用再为每个类型手动调用AddLogger<T>,直接注册一个通用的ILogger<T>实现,让DI自动为任意类型提供Logger实例:

修改ServiceCollectionExtensions:

public static class ServiceCollectionExtensions
{
    // 一次性注册通用Mock Logger,覆盖所有ILogger<T>需求
    public static IServiceCollection AddMockLogger(this IServiceCollection services)
    {
        services.AddSingleton(typeof(ILogger<>), typeof(MockLogger<>));
        return services;
    }

    // 通用Mock Logger实现,可按需添加日志逻辑
    private class MockLogger<T> : ILogger<T>
    {
        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
        public bool IsEnabled(LogLevel logLevel) => true;
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            // 可选:在这里输出测试日志,或者留空不处理
        }
    }
}

更新TestBase,只需调用一次通用注册:

public abstract class IntegrationTestBase<TService, TImplementation>
where TService : class
where TImplementation : class, TService
{
    protected TService Sut(Action<IServiceCollection>? configureService = default)
    {
        var services = new ServiceCollection();
        services.AddMockLogger(); // 替代原来的AddLogger<TImplementation>
        services.AddSut<TService, TImplementation>();

        configureService?.Invoke(services);

        return services.BuildServiceProvider().GetRequiredService<TService>();
    }
}

测试类里的InitServices也不用再手动加AddLogger<B>了:

private static void InitServices(IServiceCollection services, bool refreshTokenGrantEnabled = false, bool passwordGrantEnabled = false, bool stsEnabled = true)
{
     services.AddSingleton<B>();
     // 移除AddLogger<B>,通用Logger会自动处理
}

2. 基于Moq的通用注册方案

如果坚持用Mock.Of<ILogger<T>>,可以用包装类实现通用注册:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMockLogger(this IServiceCollection services)
    {
        services.AddSingleton(typeof(ILogger<>), typeof(MockLoggerWrapper<>));
        return services;
    }

    private class MockLoggerWrapper<T> : ILogger<T>
    {
        private readonly ILogger<T> _mockInstance;

        public MockLoggerWrapper()
        {
            _mockInstance = Mock.Of<ILogger<T>>();
        }

        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => _mockInstance.BeginScope(state);
        public bool IsEnabled(LogLevel logLevel) => _mockInstance.IsEnabled(logLevel);
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
            => _mockInstance.Log(logLevel, eventId, state, exception, formatter);
    }
}

3. 额外优化:自动注册依赖链

如果想进一步减少InitServices的维护成本,可以用DI自动装配(比如Scrutor库扫描程序集注册服务),避免手动逐个添加依赖:

public abstract class IntegrationTestBase<TService, TImplementation>
where TService : class
where TImplementation : class, TService
{
    protected TService Sut(Action<IServiceCollection>? configureService = default)
    {
        var services = new ServiceCollection();
        services.AddMockLogger();
        services.AddSingleton<TService, TImplementation>();
        
        // 用Scrutor自动注册当前程序集内的所有服务(需先安装Scrutor包)
        services.Scan(scan => scan
            .FromAssemblyOf<TImplementation>()
            .AddClasses()
            .AsImplementedInterfaces()
            .WithSingletonLifetime());

        configureService?.Invoke(services);

        return services.BuildServiceProvider().GetRequiredService<TService>();
    }
}

这样类A依赖的类B会被自动注册,不用再手动写services.AddSingleton<B>(),减少配置变更的风险。


内容的提问来源于stack exchange,提问作者user1263981

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 23:22:54