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

.NET 7类库扩展方法中如何使用主应用提供的日志抽象?

问题

我有一个基于.NET 7的C#类库,通过扩展方法被WPF/控制台应用初始化。类库不依赖任何外部日志提供程序,需使用主应用提供的日志服务。类库内的服务(如MyLibService1、MyLibService2)通过ILogger<>依赖注入日志正常,但在扩展方法中无法实现日志记录。

类库简化初始化代码如下:

public static class AddMyLibraryExtensions
{
    public static HostApplicationBuilder AddMyLibrary(this HostApplicationBuilder builder)
    {
        var libConfiguration = builder.Configuration.GetRequiredSection("MyLibrary");

        builder.Services.AddOptions<MyLibrarySettings>()
            .Bind(libConfiguration);
        
        builder.Services.TryAddTransient<IMyLibService1, MyLibService1>();
        builder.Services.TryAddTransient<IMyLibService2, MyLibService2>();

        var libSettings = libConfiguration.Get<MyLibrarySettings>();

        builder.Services.AddDbContext<MyLibDbContext>(opts =>
        {
            opts.UseSqlite(libSettings.ConnectionString);
        });
        
        return builder;
    }
}

主应用使用方式如下(使用CreateApplicationBuilder有诸多优势):

static void Main(string[] args)
{
        var builder = Host.CreateApplicationBuilder();
        builder.Services.AddTransient<IAppService1, AppService1>();
        builder.Services.AddTransient<IAppService2, AppService2>();

        builder.AddMyLibrary(); // 类库可能在此处添加...

        builder.Services.AddLogging(cfg =>
        {
            var logConf = new LoggerConfiguration()
                .ReadFrom.Configuration(builder.Configuration)
                .CreateLogger();

            Log.Logger = logConf;

            cfg.AddSerilog(logConf); // 示例使用Serilog
        });

        // builder.AddLibrary(); // 或在此处添加...
        var host = builder.Build();

        var job = host.Services.GetService<IAppService1>();
        job.DoWork();
}

类库无法知晓日志何时添加(是否添加)、使用何种提供程序,且扩展方法中无法使用依赖注入,宿主构建完成前也无法使用宿主的服务定位器。

请问有没有办法在AddMyLibrary方法中使用主应用的日志服务?目前我想到的方式是将应用日志作为参数传入扩展方法,如.AddMyLibrary(object logger),但这种方式不够优雅。

解决方案

方法1:利用HostApplicationBuilder内置日志工厂

HostApplicationBuilder本身内置了临时日志工厂(builder.Logging),可以直接创建ILogger实例,即使主应用后续配置了完整日志提供程序,临时日志也会自动桥接到最终系统,不会丢失日志条目。

修改类库扩展方法:

public static class AddMyLibraryExtensions
{
    public static HostApplicationBuilder AddMyLibrary(this HostApplicationBuilder builder)
    {
        // 创建类库专属日志实例
        var logger = builder.Logging.CreateLogger<AddMyLibraryExtensions>();

        try
        {
            var libConfiguration = builder.Configuration.GetRequiredSection("MyLibrary");
            logger.LogInformation("正在加载MyLibrary配置");

            builder.Services.AddOptions<MyLibrarySettings>()
                .Bind(libConfiguration);
            
            builder.Services.TryAddTransient<IMyLibService1, MyLibService1>();
            builder.Services.TryAddTransient<IMyLibService2, MyLibService2>();

            var libSettings = libConfiguration.Get<MyLibrarySettings>();
            if (libSettings == null)
            {
                logger.LogError("MyLibrary配置加载失败");
                throw new InvalidOperationException("MyLibrary配置无效");
            }

            builder.Services.AddDbContext<MyLibDbContext>(opts =>
            {
                opts.UseSqlite(libSettings.ConnectionString);
                logger.LogInformation("已配置SQLite数据库连接: {ConnectionString}", libSettings.ConnectionString);
            });
            
            logger.LogInformation("MyLibrary初始化完成");
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "MyLibrary初始化出错");
            throw;
        }

        return builder;
    }
}

方法2:延迟初始化到宿主启动时

如果需要依赖完整配置的日志系统,可将需要日志的初始化逻辑延迟到宿主构建完成后,通过IHostedService执行,确保使用主应用最终配置的日志服务:

  1. 在类库中添加初始化托管服务:
internal class MyLibraryInitializerHostedService : IHostedService
{
    private readonly ILogger<MyLibraryInitializerHostedService> _logger;
    private readonly MyLibrarySettings _settings;
    private readonly MyLibDbContext _dbContext;

    public MyLibraryInitializerHostedService(ILogger<MyLibraryInitializerHostedService> logger,
        IOptions<MyLibrarySettings> settings,
        MyLibDbContext dbContext)
    {
        _logger = logger;
        _settings = settings.Value;
        _dbContext = dbContext;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("开始执行MyLibrary延迟初始化");
        
        // 这里可执行需要日志的初始化逻辑,比如数据库迁移
        await _dbContext.Database.EnsureCreatedAsync(cancellationToken);
        _logger.LogInformation("MyLibrary数据库初始化完成");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}
  1. 在扩展方法中注册该服务:
public static HostApplicationBuilder AddMyLibrary(this HostApplicationBuilder builder)
{
    var libConfiguration = builder.Configuration.GetRequiredSection("MyLibrary");

    builder.Services.AddOptions<MyLibrarySettings>()
        .Bind(libConfiguration);
    
    builder.Services.TryAddTransient<IMyLibService1, MyLibService1>();
    builder.Services.TryAddTransient<IMyLibService2, MyLibService2>();

    builder.Services.AddDbContext<MyLibDbContext>(opts =>
    {
        var libSettings = libConfiguration.Get<MyLibrarySettings>();
        opts.UseSqlite(libSettings.ConnectionString);
    });

    // 注册延迟初始化服务
    builder.Services.AddHostedService<MyLibraryInitializerHostedService>();
    
    return builder;
}

方法3:优雅接收日志工厂参数

如果觉得直接传logger不够优雅,可以改为接收ILoggerFactory参数,类库可自主创建符合命名空间的日志实例,同时保持与主应用日志系统解耦:

修改扩展方法签名:

public static HostApplicationBuilder AddMyLibrary(this HostApplicationBuilder builder, ILoggerFactory loggerFactory = null)
{
    // 优先使用传入的工厂,否则用builder内置的
    var logger = (loggerFactory ?? builder.Logging).CreateLogger<AddMyLibraryExtensions>();
    
    // 后续初始化逻辑同方法1...
}

主应用调用时可选择传入:

// 若已配置日志,可传入LoggerFactory
var loggerFactory = builder.Services.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
builder.AddMyLibrary(loggerFactory);

也可保持无参调用,自动使用内置日志。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 11:27:12