C
C#6d ago
Jacko

How to configure services in my cli application:

var container = new ContainerBuilder()
.WithSystemConfiguration()
.WithConsoleVerbosityWriter()
.WithSystemServices()
.WithPlugins();
var container = new ContainerBuilder()
.WithSystemConfiguration()
.WithConsoleVerbosityWriter()
.WithSystemServices()
.WithPlugins();
So I have code like above where I'm creating a container using autofac and then registering some services with methods like
public static ContainerBuilder WithConsoleVerbosityWriter(this ContainerBuilder container)
{
var logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} {SourceContext:l} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
container.RegisterInstance(logger).As<ILogger>();
return container;
}
public static ContainerBuilder WithConsoleVerbosityWriter(this ContainerBuilder container)
{
var logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} {SourceContext:l} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
container.RegisterInstance(logger).As<ILogger>();
return container;
}
The issue is I want the logger to get its configuration details from the IConfiguraton supplied by WithSystemConfiguration() and I'm not sure whether to use a build callback like
{
container.RegisterBuildCallback(scope =>
{
var config = scope.Resolve<IConfiguration>();
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(config)
.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} {SourceContext:l} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();

Log.Logger = logger; // Set the static Serilog logger
scope.Resolve<ILoggerFactory>().AddSerilog(logger); // Add Serilog to ILoggerFactory

{
container.RegisterBuildCallback(scope =>
{
var config = scope.Resolve<IConfiguration>();
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(config)
.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} {SourceContext:l} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();

Log.Logger = logger; // Set the static Serilog logger
scope.Resolve<ILoggerFactory>().AddSerilog(logger); // Add Serilog to ILoggerFactory

or create a wrapper class like MyCustomLogger that uses ILogger and IConfiguration in it's constructor
7 Replies
Sossenbinder
Sossenbinder6d ago
Does the config registration actually benefit from being embedded in a di registration, or does it only use static resources and you could just build it outside of the registrations, passing it as a concrete IConfiguration? Otherwise your callback with resolving the config from scope seems just fine to me on a quick glance
Jacko
JackoOP6d ago
It could be passed in I just wasn’t sure if it was a good idea writing container_builder().withLogging(config).withOtherService(config)
Sossenbinder
Sossenbinder6d ago
So to give some perspective on my thoughts - Asp net also does not exclusively expose IConfiguration through DI. It starts off the process with actually instatiating a config manager. This config is then exposed and available during the DI registration process. I could imagine this might also simplify your question, initializing config based on truly static values and just exposing it through your registration process, in case you're bending over backwards to access your config otherwise That being said, I don't see any problem in what you're doing If you're passing a registration callback to access config on the built scope, that's valid It's just like injecting IConfiguration into any class You could also think about supporting options if you're already resolving from the built container
Jacko
JackoOP6d ago
Ahh okay so you’re suggesting creating the config object before the di container so I can just pass properties into the .withLogging function as a parameter for example ? What do you mean by this ?
Sossenbinder
Sossenbinder6d ago
$options
MODiX
MODiX6d ago
appsettings.json:
{
"My": {
"Foo": {
"Kix": 5
}
}
}
{
"My": {
"Foo": {
"Kix": 5
}
}
}
src/Foo/FooOptions.cs:
public class FooOptions
{
public const string SectionName = "My:Foo";

public string Bar {get;set;} = "default value for bar";
public int Kix {get;set;} = -1;
public DateTime? Pouet {get;set;} = default;
}
public class FooOptions
{
public const string SectionName = "My:Foo";

public string Bar {get;set;} = "default value for bar";
public int Kix {get;set;} = -1;
public DateTime? Pouet {get;set;} = default;
}
src/Foo/FooServiceCollectionExtensions.cs:
namespace Microsoft.Extensions.DependencyInjection; // <==== recommanded for service.Add so that you don't clutter Startup file

public class FooServiceCollectionExtensions
{
public static IServiceCollection AddFoo(this IServiceCollection services) =>
services
.AddOptions<FooOptions>()
.BindConfiguration(FooOptions.SectionName)
.Validate(options => options.Kix >= 0, $"The configuration key '{FooOptions.SectionName}:{nameof(Kix)}' cannot be negative")
;

public static IServiceCollection AddFoo(this IServiceCollection services, Action<FooOptions> configure) =>
services
.AddFoo()
.Configure(configure);
namespace Microsoft.Extensions.DependencyInjection; // <==== recommanded for service.Add so that you don't clutter Startup file

public class FooServiceCollectionExtensions
{
public static IServiceCollection AddFoo(this IServiceCollection services) =>
services
.AddOptions<FooOptions>()
.BindConfiguration(FooOptions.SectionName)
.Validate(options => options.Kix >= 0, $"The configuration key '{FooOptions.SectionName}:{nameof(Kix)}' cannot be negative")
;

public static IServiceCollection AddFoo(this IServiceCollection services, Action<FooOptions> configure) =>
services
.AddFoo()
.Configure(configure);
Program.cs / Startup.cs:
services.AddFoo();
// or
services.AddFoo(fooOptions => fooOptions.Kix = 12);
services.AddFoo();
// or
services.AddFoo(fooOptions => fooOptions.Kix = 12);
Bar.cs:
public class Bar
{
private readonly FooOptions _fooOptions;

// .Value in ctor is fine only if it's always ever a non-changing value (no reload and/or no scoped resolution)
public Bar(IOptions<FooOptions> fooOptions)
=> _fooOptions = fooOptions.Value;
}
public class Bar
{
private readonly FooOptions _fooOptions;

// .Value in ctor is fine only if it's always ever a non-changing value (no reload and/or no scoped resolution)
public Bar(IOptions<FooOptions> fooOptions)
=> _fooOptions = fooOptions.Value;
}
Sossenbinder
Sossenbinder6d ago
Yeah Exactly 😄

Did you find this page helpful?