【问题标题】:How to access a service in ASP.NET Core before building the WebHost?如何在构建 WebHost 之前访问 ASP.NET Core 中的服务?
【发布时间】:2021-09-16 20:08:08
【问题描述】:

假设我们有以下Program.cs

public static class Program
{
    public static async Task Main(string[] args)
    {
        await CreateWebHostBuilder(args).Build().RunAsync();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args)
    {
        return WebHost
            .CreateDefaultBuilder(args)
            .ConfigureKestrel(options => { options.AllowSynchronousIO = false; })
            .ConfigureFanciness()
            .ConfigureLogging(ConfigureApplicationLogging)
            .UseStartup<Startup>();
    }

    private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
    {
        var loggingConfiguration = context.Configuration.GetSection("Logging");

        loggingBuilder.AddConfiguration(loggingConfiguration);

        // var fancyService = SomehowGet<IFancyService>();
        // if (fancyService.IsEnabled)
        //     loggingBuilder.AddEventLog(loggingConfiguration);
    }

    public static IWebHostBuilder ConfigureFanciness(this IWebHostBuilder hostBuilder)
    {
        return hostBuilder.ConfigureServices(delegate (WebHostBuilderContext context, IServiceCollection services)
        {
            var fancinessConfiguration = context.Configuration.GetSection("Fanciness");
            services.Configure<FancinessSettings>(fancinessConfiguration);
            services.AddSingleton<IFancyService, FancyService>();

            // var fancyService = SomehowGet<IFancyService>();
            // fancyService.Initialize();
        });
    }
}

还有以下FancyService.cs

public sealed class FancyService : IFancyService
{
    private readonly ILogger<FancyService> logger;
    private readonly IOptions<FancinessSettings> settings;

    public FancyService(ILogger<FancyService> logger, IOptions<FancinessSettings> settings)
    {
        this.logger = logger;
        this.settings = settings;
    }

    public bool IsEnabled { get; private set; }

    public void Initialize()
    {
        // do the initialization work and set IsEnabled to some value
    }
}

如示例文件中所示,loggingBuilder.AddEventLog(loggingConfiguration) 依赖于在调用IFancyService.Initialize() 时设置的IFancyService.IsEnabled

为此,我需要访问IFancyService 的实例;有没有办法做到这一点?

【问题讨论】:

  • 我认为更多关于该服务正在做什么以及初始化做什么的上下文对我来说是必需的,我可以看到一些这样做的方法,但这取决于服务。是不是不能在构造函数中注册初始化?
  • 虽然不确定发表评论的目的,但感觉有些颠倒,请问isEnabled的目的是什么?
  • 虽然可以在实际构建WebHost 之前引用IFancyService,但我认为您的代码设计不佳:您的IFancyService 需要ILogger&lt;&gt; 服务和一个ILogger&lt;&gt; 服务需要 ILoggerFactory 服务,它使用 ILoggerProvider 和其他选项来创建这样的实例。它现在工作得很好。但是如果要根据IFancySerivce.IsEnabled来配置日志记录行为,最终会引入一个循环依赖
  • @KieranDevlin, @Jaya,你能帮我理解为什么知道Initialize() 做什么以及IsEnabled 是什么的细节很重要吗?你可以把前者想象成我不想在构造函数中做的工作,比如从磁盘读取文件或从网络中读取数据,而前者是处理文件或流中数据的结果。是的,可以注册一个初始化的FancyService,但是我需要手动创建它的依赖关系,ILogger&lt;FancyService&gt;IOptions&lt;FancinessSettings&gt;
  • @itminus,我知道循环依赖的样子。但是,在这种情况下,CreateDefaultBuilder(args) 已经配置了默认记录器,ConfigureApplicationLogging() 只是在IFancySerivce.IsEnabled 时添加更多。无论如何,如果您有更好的想法来实现我正在尝试做的事情,请告诉我。

标签: c# asp.net-core


【解决方案1】:

一旦services.AddSingleton&lt;IFancyService, FancyService&gt;()被调用,你可以通过构建ServiceProvider来获取FancyService的实例:

private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
    var fancyService = loggingBuilder.Services.BuildServiceProvider().GetService<IFancyService>();
    fancyService.Initialize();
    var loggingConfiguration = context.Configuration.GetSection("Logging");

    loggingBuilder.AddConfiguration(loggingConfiguration);

    if (fancyService.IsEnabled)
        loggingBuilder.AddEventLog(loggingConfiguration);
}

【讨论】:

  • ConfigureApplicationLogging() 呢?
【解决方案2】:

如 cmets 中所述,如果您的 FancyServiceILogger&lt;&gt; 没有构造函数依赖:

public sealed class FancyService : IFancyService
{ 
    public FancyService(IOptions<FancinessSettings> settings)
    { ... }
 

要初始化IFancyService,只需使用实现工厂来创建IFancyService实例:

public static IWebHostBuilder ConfigureFanciness(this IWebHostBuilder hostBuilder)
{
    return hostBuilder.ConfigureServices(delegate (WebHostBuilderContext context, IServiceCollection services)
    {
        var fancinessConfiguration = context.Configuration.GetSection("Fanciness");
        services.Configure<FancinessSettings>(fancinessConfiguration);
        services.AddSingleton<IFancyService, FancyService>(sp =>{
            var fancy=ActivatorUtilities.CreateInstance(sp,typeof(FancyService)) as FancyService;
            fancy.Initialize();      
            return fancy;
        });
    });
}

同样的技巧也可以用来注册一个记录器提供者。由于ILogger&lt;&gt; 服务依赖于IEnumerable&lt;ILoggerProvider&gt;,我们可以注册一个ILoggerProvider 实例,该实例提供额外的INullLogger,以便根据当前IFancyService 配置日志记录行为:

private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
    var loggingConfiguration = context.Configuration.GetSection("Logging");

    var descriptor=ServiceDescriptor.Singleton<ILoggerProvider,NullLoggerProvider>(sp =>{
        var provider = NullLoggerProvider.Instance;
        var fancy = sp.GetRequiredService<IFancyService>();
        if(fancy.IsEnabled)      
        {
            loggingBuilder.AddDebug();
            loggingBuilder.AddEventLog(loggingConfiguration);
            loggingBuilder.AddEventSourceLogger(); 
            // ... add more configuration as you like
        }else{
            loggingBuilder.AddConsole();
        }
        return provider;
    });
    loggingBuilder.Services.TryAddEnumerable(descriptor);
    loggingBuilder.AddConfiguration(loggingConfiguration);
}

附带说明一下,一旦构建了记录器,请注意不要更改IFancyService.IsEnabled。那是因为ILogger&lt;&gt; 服务是注册为单例的,一旦创建就永远不会改变。

【讨论】:

  • 谢谢。不过,与@AlliterativeAlice 回答中建议的那样使用loggingBuilder.Services.BuildServiceProvider().GetService&lt;IFancyService&gt;(); 相比,我不清楚为什么要这样做。
  • @TheBlueSky 最近有一个类似的帖子问了一个类似的问题。见poke's answer。此外,不同的服务提供商引入不同的服务以及范围。处理此问题时必须非常小心。
  • @TheBlueSky 另外,还有一个由多个服务提供商引起的错误,请参阅stackoverflow.com/questions/53922206/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-13
  • 1970-01-01
  • 2023-01-30
  • 1970-01-01
  • 2021-11-05
  • 2021-02-27
相关资源
最近更新 更多