Custom Serilog config possible from Program.cs? - c#

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);
})

Related

Configuring Serilog before configuration is ready?

I have a web api (.NET Core 3.1) which is using Serilog for logging. Serilog is added to the IWebHostBuilder quite early:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost
.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog((context, configuration) =>
{
if (context.HostingEnvironment.IsDevelopment())
{
configuration.WriteTo.Console(LogEventLevel.Debug);
return;
}
configuration.WriteTo.ApplicationInsights(TelemetryConverter.Traces, LogEventLevel.Error);
});
}
This means (afaik) that I need to have already configured the logger at this point. So this is the very first thing I do in the main:
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.CreateLogger();
var host = CreateWebHostBuilder(args).Build();
host.Run();
}
But the line .ReadFrom.Configuration(Configuration) requires the configuration to be set up. This is usually done in the StartUp (again, afaik) which has not yet been run at this time. Obviously I could move my LoggerConfiguration to later, but the .UseSerilog would be called before it was configured.
So how do I configure Serilog with IConfugration, when I haven't set it up yet?
#RubenBartelink pointed to a very good ressource in comment.
This is also described in the Serilog for ASP.NET Core documentation.
In particular the two-stage initialization part, which states:
Two-stage initialization
The example at the top of this page shows how to configure Serilog immediately when the application starts.
This has the benefit of catching and reporting exceptions thrown during set-up of the ASP.NET Core host.
The downside of initializing Serilog first is that services from the ASP.NET Core host, including the appsettings.json configuration and dependency injection, aren't available yet.
To address this, Serilog supports two-stage initialization. An initial "bootstrap" logger is configured immediately when the program starts, and this is replaced by the fully-configured logger once the host has loaded.
To use this technique, first replace the initial CreateLogger() call with CreateBootstrapLogger():
using Serilog;
using Serilog.Events;
public class Program
{
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateBootstrapLogger(); // <-- Change this line!
Then, pass a callback to UseSerilog() that creates the final logger:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
It's important to note that the final logger completely replaces the bootstrap logger: if you want both to log to the console, for instance, you'll need to specify WriteTo.Console() in both places, as the example shows.
Consuming appsettings.json configuration
Using two-stage initialization, insert the ReadFrom.Configuration(context.Configuration) call shown in the example above. The JSON configuration syntax is documented in the Serilog.Settings.Configuration README.

Access configuration object while configuring services

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();

System Environment Variable with connection string returns null value when called in .net core 2.1

I am trying to store my connection string into my system(windows 10) environment variable to protect my sensitive information before going to production.
When I do call my environment variables, it returns a null value. I don't have any errors.
I did follow those tutorials:
https://www.youtube.com/watch?v=TNghzUs0BQI&fbclid=IwAR2YUue7740qgVT_5z04xruel-4NwOuUyjQj5E63T1UpYqRoVuz_81DiZTo
https://medium.com/#gparlakov/the-confusion-of-asp-net-configuration-with-environment-variables-c06c545ef732
I don't see what I am missing here. For what I understand, if I don't use prefixes, I juste need to set the environment variable in my system and then make the call.
Here is an image of my environment variable. I do provide a link, because I don't have enough internet points to post the image.
https://i.imgur.com/j614O1n.png
This is my Startup.cs
private IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<dbApplicationServiceNavada>(options => options.UseMySql(Environment.GetEnvironmentVariable("ASNConnStr")));
services.AddScoped<IEmployeeData, SqlEmployeeData>();
services.AddScoped<ILanguageData, SqlLanguageData>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
IConfiguration configuration)
{
//Show exception stack trace when in dev mode
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Use files in wwwroot folder
app.UseStaticFiles();
//Defines the available routes
app.UseMvc(ConfigureRoutes);
app.Run(async (context) =>
{
var RNA = configuration["RouteNA"];
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(RNA);
});
}
This is my webHostBuilder in Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables();
})
.UseStartup<Startup>()
.Build();
I also see that I have a launchSettings.json file with Environment variables in there, but I wonder if it's the same thing as system environement variables.
FIRST EDIT:
As mentioned in the answer, I did rename my environment variable to
MYSQLCONNSTR_ASNConnStr
I also removed the part in my program.cs to load environmental variable.
Now when I debug the project, I see in my _confirguration.Providers my environment variable getting loaded.
Here is a picture:
https://i.imgur.com/xGulUvX.png
When I do
services.AddDbContext<dbApplicationServiceNavada>(options => options.UseMySql(_configuration.GetConnectionString("ASNConnStr")));
It still returns a null value. I don't seem to be able to get the variable.
Picture of the error:
https://i.imgur.com/Kj0mvCS.png
SECOND EDIT:
Now the problem is solved, I just want to add to the contributor Simply Ged, that instead of :
services.AddDbContext<dbApplicationServiceNavada>(options => options.UseMySql(_configuration.GetConnectionString("ASNConnStr")));
I had to do:
services.AddDbContext<dbApplicationServiceNavada>(options => options.UseMySql(Environment.GetEnvironmentVariable("ASNConnStr")));
Now everything works fine!
THIRD EDIT:
After restarting my computer, this line of code works:
services.AddDbContext<dbApplicationServiceNavada>(options => options.UseMySql(_configuration.GetConnectionString("ASNConnStr")));
It appears that we need to restart the computer/server in order to get the environmental variable adequatly.
In Conclusion, the answer of the contributor Simply Ged was right.
You don't need to add ConfigureAppConfiguration to add environment variables to the configuration as it is done as part of WebHost.CreateDefaultBuilder. See this page for details about what the default builder provides.
Knowing that, you can replace read your connection string from the Configuration class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<dbApplicationServiceNavada>(options => options.UseMySql(Configuration.GetConnectionString("ASNConnStr")));
....
}
The Configuration class will load the correct value based on the order that is defined here
You would also need to change your environment variable name to the following:
MYSQLCONNSTR_ASNConnStr
You can read more about the Configuration in .NET Core here

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.

.net Core Access appsettings.json When Configuring a Different Configuration Provider

I'm just getting started with .net Core 2.1 and I have written a custom ConfigurationProvider that loads my sensitive configuration from a separate file. This works great with a hard-coded path but I also want the location of the file to be stored in configuration. If I try to access the appsettings config during the setup of my provider the values aren't there. Presumably this is because I am in the configuration phase still. Is there a way of accessing them or failing that is there a way to add another config provider inside Startup.cs instead of Program.cs?
This is what I am trying to do:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
logging.ClearProviders();
logging.AddConsole();
logging.AddFile(opts =>
{
hostingContext.Configuration.GetSection("FileLoggerOptions").Bind(opts);
});
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
config.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true);
config.AddEncryptedFile(hostingContext.Configuration["SecureSettingsFile"], true, true);
})
.UseStartup<Startup>();
}
As you can see I am able to access the config in the logging config but not the app config. My provider is the AddEncryptedFile bit and hostingContext.Configuration["SecureSettingsFile"] should read the value from appsettings.json.
The only configuration available at this stage is Environment variables and I don't really want to use those as the server has about 30 websites on it.
Is what I am trying do possible or will I have to use an Environment variable or store my encrypted config in the same location as my website?
thanks
You'll need to use a ConfigurationBuilder to initially just read appsettings.json, and use the output of the builder to do your full configuration. Something like this:
.ConfigureAppConfiguration((hostingContext, config) =>
{
var baseConfig = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var secureSettingsFile = baseConfig.GetValue<string>("SecureSettingsFile");
/* ... */
config.AddEncryptedFile(secureSettingsFile, true, true);
})

Categories

Resources