如何让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
相关产品推荐
相关产品推荐

