Access configuration object while configuring services - c#

In a console application, rather than building the IConfiguration and IServiceProvider manually, I'm trying to use the Host.CreateDefaultBuilder() process:
IHost host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddSingleton<Whatever>();
})
.Build();
I can get the configuration object after building the host. But what I'm looking for is a way to get the config object while still in the ConfigureServices body, so that I can bind a config section to the service provider.
Something like:
AccountConfiguration accountConfig = new();
config.Bind("AccountConfiguration", accountConfig);
services.AddSingleton(accountConfig);
// or
services.Configure<AccountConfiguration>(config.GetSection("AccountConfiguration"));
Is there a way to access the config object while still configuring the services? Or a good way of adding an object to the service collection after the host has already been built?

The first parameter of lambda passed to ConfigureServices is HostBuilderContext which exposes configuration property - IConfiguration Configuration:
IHost host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
IConfiguration config = context.Configuration;
// use config
services.AddSingleton<Whatever>();
})
.Build();

Related

Unable to configure Autofac to inject Serilog as implementation of ILogger in .NET 6 web app

I'm struggling to configure Serilog as the implementation for ILogger via Autofac in a .NET 6 web app.
Here's what I have so far...
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration().ReadFrom
.Configuration(configuration)
.CreateBootstrapLogger();
var builder = WebApplication.CreateBuilder(args)
.ConfigureKeyVaultAppConfiguration<Program>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
void RegisterModules()
{
containerBuilder.RegisterModule<DataAccessModule>();
}
void RegisterConfigs()
{
containerBuilder.RegisterAllConfigurationsInAssembly(typeof(PersistenceSettings).Assembly);
}
RegisterModules();
RegisterConfigs();
})
.UseSerilog((ctx, lc) => lc.WriteTo.Console()
.ReadFrom.Configuration(ctx.Configuration));
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(Log.Logger);
var app = builder.Build();
app.UseSerilogRequestLogging();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
And when I run it...
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type '[My Class]' can be invoked with the available services and parameters: Cannot resolve parameter 'Microsoft.Extensions.Logging.ILogger logger' of constructor 'Void .ctor(Supertext.Language.Types.PersistenceSettings, Microsoft.Extensions.Logging.ILogger)'.
I can see plenty of other devs with the same problem (ex. 1, ex. 2), but their solutions haven't solved my problem.
Make sure you're injecting ILogger<T> like ILogger<MyClass> and not just plain old ILogger. I don't think there is configuration to inject an untyped logger because loggers need to know what category (what the source class is) that they're logging for in order to get created. (That's not an Autofac thing, that's a .NET Core logging thing.)
If you need to provide a string as a category for some reason and manually get just an ILogger, you need to inject ILoggerFactory and create the logger from there.

Configure a logging provider for a different service collection

This is an ASP.NET application in .NET 6. There's a wizard interface where the user inputs some data and then based on the input, I set up a new dependency injection container with the services that are required to complete the task. My problem is that the ILogger<> instances coming out of this second container don't use the custom ILoggingProvider that I set up.
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddDebug();
builder.Logging.AddSignalRLogging();
public static class ILoggingBuilderExtensions
{
public static ILoggingBuilder AddSignalRLogging(this ILoggingBuilder builder)
=> builder.AddProvider(new SignalRLoggerProvider(builder.Services))
.AddFilter<SignalRLoggerProvider>("MyNamespace", LogLevel.Information);
}
The SignalRLoggerProvider comes from How to implement an ILogger to send messages to a SignalR Hub?.
Controller:
IServiceCollection services = new ServiceCollection();
services.AddLogging();
services.AddSignalR();
services.AddSingleton(_sheetsClient); // this was injected into the controller
services.AddSingleton<ITeamReaderService>(new PostedTeamReaderService(model.Divisions));
string[] divisionNames = model.Divisions.Keys.ToArray();
foreach (string divisionName in divisionNames)
{
services.AddSingleton<IDivisionSheetService>(provider => new DivisionSheetService(divisionName,
provider.GetRequiredService<StandingsRequestCreatorFactory>(),
provider.GetRequiredService<ISheetsClient>(),
provider.GetRequiredService<IOptionsMonitor<ScoreSheetConfiguration>>(),
provider.GetRequiredService<ILogger<DivisionSheetService>>())
);
}
I know my provider works because when I log things in a controller whose dependencies were injected from the HostBuilder's service collection (_sheetsClient), those messages work correctly. In classes that come from this other container (DivisionSheetService), those log messages go nowhere and when I view the ILogger instance in the debugger, it shows that it has no logger that it's writing to.
So it must be the case that my custom logging provider is unknown to the second container, but I can't figure out how to register it there.
Thanks in advance.
Since you're creating a new ServiceCollection from scratch, you also need to add the logging infrastructure from scratch:
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder => builder.AddDebug().AddSignalRLogging());

How to access Log4Net after adding it to Dependency Injection

This is my first time working with Microsoft.Extensions.Logging.Log4Net.AspNetCore.
I have a WPF application where I add the Log4Net provider within the CreateDefaultBuilder in App method of App.xaml.cs. Immediately after, I want to write to the log file using LogInformation saying "Starting Application". From what I can tell, it does not seem like I can do this because of the way it has been added to the Dependency Injection container. I must either call a method that accesses the DI container in the parameter list or add Log4Net to the ServiceProvider and then retrieve the service that way. But, that does not seem right because I will effectively have Log4Net added to DI twice.
Is there a way to immediately access the DI container after configuring Log4Net so I can write to the log file?
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, appBuilder) =>
{
// Do some stuff here
}).ConfigureLogging(logBuilder =>
{
logBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
logBuilder.AddLog4Net("log4net.config");
})
.ConfigureServices((context, services) =>
{
Configuration = context.Configuration;
ConfigureServices(Configuration, services);
})
.Build();
ServiceProvider = Host.Services;
// How to access Log4Net here?
_ISomeLogger.LogInformation("Starting Application");
Update
I am using the following example: https://www.thecodebuzz.com/logging-using-log4net-net-core-console-application/
The solution is to access the Log4Net from the ServiceProvider right after building it:
logger = (ILogger<Program>)ServiceProvider.GetService(typeof(ILogger<Program>));

Custom Serilog config possible from Program.cs?

I'm generating a custom config in ASP.NET Core during the Startup constructor, something like this:
public Startup(IWebHostEnvironment environment, IConfiguration configuration) {
Environment = environment;
// Add extra config - configs added later override ones added earlier when key names clash
Configuration = new ConfigurationBuilder()
.AddConfiguration(configuration)
.AddXmlFile("appsettings.xml")
.Build();
}
My problem is that, while this new IConfiguration can be accessed from ConfigureServices and Configure, it can't be accessed from the .UseSerilog() call in CreateHostBuilder (Program.cs). So, I can't access my XML config at the time of my call:
webBuilder
.UseStartup<Startup>()
.UseSerilog((context, config) => {
config.ReadFrom.Configuration(context.Configuration);
});
How can I get around this? Can I make the new configuration available to the UseSerilog() lambda, or can I configure the logger later, in ConfigureServices()?
OK, I found out I can just .UseConfiguration() earlier in the IWebHostBuilder chain (instead of in the Startup constructor) to pull in my XML config settings:
webBuilder
.UseConfiguration(new ConfigurationBuilder().AddXmlFile("appsettings.xml").Build())
.UseSerilog((context, config) => {
config.ReadFrom.Configuration(context.Configuration);
})

DbContext connection string and hosted service

I have a. NET core console app that implement IHostedService and a reference to another project with a DbContext definition.
This is the configuration of DbContext in console app:
IHost host = new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost.SetBasePath(Directory.GetCurrentDirectory());
configHost.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configHost.AddCommandLine(args);
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configApp.AddJsonFile($"appsettings.json", true);
configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
configApp.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<MyAppDbContext>(options => options.UseNpgsql(hostContext.Configuration.GetConnectionString("DefaultConnection")));
services.AddHostedService<ApplicationLifetimeHostedService>();
})
.Build();
Now, in the OnStarted() method of ApplicationLifetimeHostedService I have:
using (var _context = new MyAppDbContext())
{
...
_context.SaveChanges();
}
Why MyAppDbContext take the connection string value from OnConfiguring method of dbcontext definition class (hard-coded, generated from scaffolding), and not from appsettings.{ASPNETCORE_ENVIROMENT}.json ()?
Thank you in advance!
Based on your configuration, currently the IHostBuilder is for non web applications and simulates a generic configuration, eventually this will replace the IWebHostBuilder. However, you do not need those... In your instance you would be better off with CreateDefaultBuilder.
Host Configuration
App Configuration
Both are provided by default, with more granular control. The primary item is the default services provided by the builder and what they compile or build.
To directly answer your issue though, in your top line, you are missing the following:
var host = new HostBuilder()
.ConfigureHostConfiguration(configuration =>
{
// For brevity, removed some.
configuration.AddJsonFile("appsettings.json", false, true);
}
That is why your appsettings.json is not working. The ConfigureHostConfiguration will carry through to the ConfigureAppConfiguration.
Host configuration automatically flows to app configuration
(ConfigureAppConfiguration and the rest of the app).
No providers are included by default. You must explicitly specify
whatever configuration providers the app requires in
ConfigureHostConfiguration, including:
File configuration (for example, from a hostsettings.json file).
Environment variable configuration.
Command-line argument configuration.
Any other required configuration providers.

Categories

Resources