Search code examples
.net-coresimple-injector

Simple Injector LoggerFactory in IHostBuilder.ConfigureServices bootstrap


I am using Simple Injector with the generic hosts nuget package, all is well except I have hit a snag with order of registration.

I have a builder class that returns two interfaces, IMessagePublisher and IMessageSubscriber, this builder class also requires a ILoggerFactory.

I am not able to use the ILoggerFactory as it is not yet configured, and cannot call container.GetInstance<ILoggerFactory> since the container is still being bootstrapped with registrations when the code is called.

My main host is here:

var host = CreateHostBuilder(args)
   .UseSerilog((hostContext, loggerConfiguration) =>
   {
      var setting = hostContext.Configuration
        .GetSection(Settings.Name).GetAndAssert<Settings>();

      if (setting.IsDebug == true)
          loggerConfiguration.MinimumLevel.Debug();
      else
          loggerConfiguration.MinimumLevel.Information();

      loggerConfiguration
          .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
          .Enrich.FromLogContext()
          .WriteTo.Console()
          .WriteTo.File("logging.log");
  })
  .Build()
  .UseSimpleInjector(container);

And my CreateHostBuilder is below (where my issue is in the builder.Create method):

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host
        .CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostContext, builder) =>
        {
            if (hostContext.HostingEnvironment.IsDevelopment())
            {
                builder.AddUserSecrets<Program>();
            }
        })
        .ConfigureServices((hostContext, services) =>
        {
            services.AddSimpleInjector(container, options =>
            {
                options.AddHostedService<Worker>();
                options.AddLogging();

                var mqttSettings = hostContext                                   
                    .Configuration
                    .GetSection(MqttSettings.Name)
                    .GetAndAssert<MqttSettings>();

                var builder = new MqttMessageBusBuilder(mqttSettings);

                builder.Create(
                    new[] { new MessagePackSerializer() },
                    new IdentifyUsingTransportHeaders(),
                    /* below is not allowed */
                    container.GetInstance<ILoggerFactory>,
                    // i also dont know where to get IloggerFactory if not yet configured
                    out var messagePublisher,
                    out var messageSubscriber,
                    mqttSettings.isDebug);

                container.RegisterInstance(messagePublisher);
                container.RegisterInstance(messageSubscriber);
            });
        });
}

I am not able to use a Singleton and defer the instantiation until needed, since I need the builder to return the IMessagePublisher and IMessageSubscriber instances.

I would otherwise have used the below where container.GetInstance<ILoggerFactory>() is valid:

// register factory to create PolygonWebsocket
container.RegisterSingleton( () => {
    var settings = container.GetInstance<Settings>();
    var secrets = container.GetInstance<Secrets>();

    return new PolygonWebsocket(
            secrets.PolygonIoApiKey,
            settings.PolygonWebSocketUrl,
            settings.PolygonReconnectTimeout,
            container.GetInstance<ILoggerFactory>()
    });

How can I get the ILoggerFactory injected into my MqttMessageBusBuilder.Create method during CreateHostBuilder bootstrap?


Solution

  • This problem is not related to Simple Injector, because that ILoggerFactory comes from the IServiceCollection and its final IServiceProvider and this is a chicken-and-the-egg dilemma. Your want to supply ILoggerFactory to MqttMessageBusBuilder.Create during ConfigureServices while the ILoggerFactory can only be resolved from IServiceProvider when it comes available, which is at a later stage.

    To my knowledge, you can do three things:

    1. Pull in the ILoggerFactory from the IServiceCollection, since that registration at that point already exists in the IServiceCollection.
    2. Postpone the registration of your message interfaces untal after ConfigureServices (i.e. inside Configure)
    3. Make the registrations now, but use lazy initialization so that is built when the message interfaces are resolved.

    Example for 1:

    .ConfigureServices((hostContext, services) =>
    {
        var factory = (ILoggerFactory)services
            .Last(s => s.ServiceType == typeof(ILoggerFactory))
            .ImplementationInstance;
    
        services.AddSimpleInjector(container, options =>
        {
            ...
    
            var builder = new MqttMessageBusBuilder(mqttSettings);
    
            builder.Create(
                new[] { new MessagePackSerializer() },
                new IdentifyUsingTransportHeaders(),
                factory,
                out var messagePublisher,
                out var messageSubscriber,
                mqttSettings.isDebug);
    
            container.RegisterInstance(messagePublisher);
            container.RegisterInstance(messageSubscriber);
        });
    });
    

    Example for 2:

    var host = 
        CreateHostBuilder(args)
        .UseSerilog((hostContext, loggerConfiguration) =>
        {
            ...
        })
        .Build()
        .UseSimpleInjector(container);
    
    var factory = host.Services.GetRequiredInstance<ILoggerFactory>();
    
    var mqttSettings = hostContext                                   
        .Configuration
        .GetSection(MqttSettings.Name)
        .GetAndAssert<MqttSettings>();
    
    var builder = new MqttMessageBusBuilder(mqttSettings);
    
    builder.Create(
        new[] { new MessagePackSerializer() },
        new IdentifyUsingTransportHeaders(),
        factory,
        out var messagePublisher,
        out var messageSubscriber,
        mqttSettings.isDebug);
    
    container.RegisterInstance(messagePublisher);
    container.RegisterInstance(messageSubscriber);
    
    return host;
    

    Example for 3:

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        return Host
            .CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostContext, builder) =>
            {
                if (hostContext.HostingEnvironment.IsDevelopment())
                {
                    builder.AddUserSecrets<Program>();
                }
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddSimpleInjector(container, options =>
                {
                    ...
    
                    var lazy = new Lazy<(
                        IMessagePublisher Publisher,
                        IMessageSubcriber Subscriber)>(() =>
                    {
                        var builder = new MqttMessageBusBuilder(mqttSettings);
                    
                        builder.Create(
                            new[] { new MessagePackSerializer() },
                            new IdentifyUsingTransportHeaders(),
                            container.GetInstance<ILoggerFactory>(),
                            out var messagePublisher,
                            out var messageSubscriber,
                            mqttSettings.isDebug);
                    
                        return (messagePublisher, messageSubscriber);
                    });
    
                    container.RegisterSingleton(() => lazy.Value.Publisher);
                    container.RegisterInstance(() => lazy.Value.Subscriber);
                });
            });
    }