I know there are several questions on this topic but haven't found anything working for me.
My problem seems to be that there is some kind of circular reference because I keep hitting the code in UseSerilog() over and over. I have tried to inject several different services but all get the same problem. In program.cs I have:
public static void Main()
{
var host = new HostBuilder()
.ConfigureAppConfiguration(config =>
{
var environmentName = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT");
if (environmentName.IsDevelopment())
{
config.AddJsonFile($"local.settings.json", optional: true, reloadOnChange: false);
}
config.AddEnvironmentVariables();
})
.UseSerilog((context, services, configuration) =>
{
configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.WintSink(services.GetRequiredService<IMyRequiredService>());
})
.ConfigureServices(ConfigureServices)
.Build();
host.Run();
}
Update 1:
I have managed to get it working if I use the Serilog.ILogger interface instead of Microsoft.Extensions.Logging.ILogger (not what I wanted) in combination with:
services.AddSingleton<Serilog.ILogger>(sp =>
{
var service = sp.GetRequiredService<IMyRequiredService>();
return new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.WintSink(service)
.CreateLogger();
});
services.AddLogging(configure => configure.ClearProviders().AddSerilog());
Related
This question already has answers here:
How to select different app.config for several build configurations
(10 answers)
Closed 26 days ago.
I'm developing a .NET Core (.NET 6) WPF app, and I have a problem detecting the dev environment (Development or Production).
I'm creating a IHost when my WPF app starts, in order to use Dependency Injection and all the other .NET Core goodies, like this:
public partial class App : Application
{
private readonly IHost host;
public App()
{
host = Host.CreateDefaultBuilder()
.UseContentRoot(CoreConstants.MaintenanceToolBinFolder)
.ConfigureServices((context, services) =>
{
var configuration = context.Configuration;
//...
})
.Build();
}
}
Now, in an ASP.net Core web app this would automatically read the ASPNETCORE_ENVIRONMENT environmental variable and use it to determine the current environment. However, this is completely ignored here, and the environment is always "Production".
What is the proper way to detect the environment in this case? Should I just manually read the variable and set the environment, or is there a more "proper" way?
I just did my very first "ASP-like" WPF app and it works like a charm.
Here is my code, with Dependency Injection and Serilog logger configuration as bonus:
public partial class App : Application
{
private IHost? _host;
protected override void OnStartup(StartupEventArgs e)
{
Startup? startup = null;
this._host = Host.CreateDefaultBuilder(e.Args)
.ConfigureHostConfiguration(config =>
{
config.AddEnvironmentVariables(prefix: "APP_");
})
.ConfigureAppConfiguration((hostingContext, configBuilder) =>
{
configBuilder.Sources.Clear();
IHostEnvironment env = hostingContext.HostingEnvironment;
configBuilder
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false)
;
if (env.IsDevelopment())
{
configBuilder.AddUserSecrets(Assembly.GetExecutingAssembly());
}
IConfiguration configuration = configBuilder.Build();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.ReadFrom.Configuration(configuration)
.CreateLogger();
Log.Information("*** Starting application ***");
})
.ConfigureServices((_, services) =>
{
services.AddSingleton< ... >();
...
})
.UseSerilog()
.Build()
;
IServiceProvider sp = this._host.Services;
var window = sp.GetRequiredService<MainWindow>();
window.Show();
}
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
this._host?.Dispose();
}
}
Also remember to configure the launch settings in the Project Property window or directly in the launchSettings.json file:
{
"profiles": {
"<app name>": {
"commandName": "Project",
"environmentVariables": {
"APP_ENVIRONMENT": "Development"
}
}
}
}
Note: I'm using .NET 7.
Since you are using generic host (Host.CreateDefaultBuilder()) you should be able to use DOTNET_ENVIRONMENT variable. From docs:
Loads host configuration from:
Environment variables prefixed with DOTNET_.
I'm trying to create a Windows Service using NET 6 and documentation found here.
I'd like to use strongly typed configuration pattern, so I modified startup code this way:
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "My Service";
})
.ConfigureAppConfiguration((hostingContext, configuration) =>
{
configuration.Sources.Clear();
IHostEnvironment env = hostingContext.HostingEnvironment;
configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
})
.ConfigureServices((hostingContext, services) =>
{
services.AddSingleton<MyService>();
services.Configure<AppSettings>(hostingContext.Configuration.GetSection("AppSettings"));
services.AddHostedService<WindowsBackgroundService>();
})
.Build();
await host.RunAsync();
Then in MyService.cs:
private AppSettings _appSettings;
public MyClass(AppSettings appSettings)
{
_appSettings = appSettings;
}
This gives me the following exception:
System.InvalidOperationException: 'Unable to resolve service for type 'StatSveglia.Service.AppSettings' while attempting to activate 'StatSveglia.Service.SvegliaService'.'
It seems that this line has no effect:
services.Configure<AppSettings>(hostingContext.Configuration.GetSection("AppSettings"));
How should I change my code to use configuration injection?
A side question: in the example service found in documentation, services.AddHttpClient<JokeService>(); is used to add the service. My service is not an HTTP client, so I preferred .AddSingleton<>. Is this a good choice?
After further reading I found out that the line:
services.Configure<AppSettings>(hostingContext.Configuration.GetSection("AppSettings"));
registers for dependency injection the class IOptions<AppSettings> and not AppSettings itself.
The correct use is therfore:
private IOptions<AppSettings> _appSettings;
public MyClass(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings;
}
private void SomeMethod()
{
var mySetting = _appSettings.Value.MySetting
}
I need to add another configuration file to my .Net Core Blazor project.
Other sources (such as this) mention using configuration builder in .Net core 2.2, but since in 3.1 the configuration is passed as a parameter into Startup, I have no way of appending another config onto it as far as I can tell. Maybe I'm looking at it wrong, but I'm assuming that the configuration passed in as a param has important configuration properties, therefor building it my self with just the additional config file seems like it leave those config properties out.
Ex: Doesn't work but should paint a picture of what I'm trying to do.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
var builder = new ConfigurationBuilder(configuration)
.AddJsonFile("accountconstants.json", optional: true, reloadOnChange: true);
});
}
You can do it in Program.cs i.e. earlier in the pipeline rather than in Startup.cs.
Example:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostContext, config) =>
{
var env = hostContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Your approach should work, but needs a little bit of tweaking.
The configuration that you create needs to be added to the DI Container.
The documentation for this is here.
I've achieved it as well in Azure Functions and other projects through the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(provider => new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddJsonFile("accountconstants.json", optional: true, reloadOnChange: true)
.Build());
}
Before initializing the application Host I need to read some settings from the application's configuration to setup some other things.
In ASP .NET Core 2.x to read settings before initializing the application Host I used to do the following:
public static void Main(string[] args)
{
//...
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddJsonFile("appsettings.json")
.Build();
//Do something useful with the configuration...
var host = WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.UseConfiguration(configuration)
.Build();
//...
}
In ASP .NET Core 3.x WebHost has been deprecated in favor of .NET Generic Host.
.NET Generic Host has only .ConfigureHostConfiguration() and .ConfigureAppConfiguration() that do not take a built configuration as parameter, but instead accept only a delegate used to setup the configuration.
For HTTP workloads you can still use the method .UseConfiguration() has it is exposed by IWebHostBuilder and do essentially the same as before:
public static void Main(string[] args)
{
//...
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddJsonFile("appsettings.json")
.Build();
//Do something useful with the configuration...
var host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseConfiguration(configuration);
})
.Build();
//...
}
But this only works for HTTP workloads and not for Worker Services.
To get the configuration before setting up the Host I've come up with the following approach:
public static void Main(string[] args)
{
//...
var configuration = ConfigureConfigurationBuilder(args)
.Build();
//Do something useful with the configuration...
var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder => ConfigureConfigurationBuilder(args, builder))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
//...
}
public static IConfigurationBuilder ConfigureConfigurationBuilder(string[] args, IConfigurationBuilder configurationBuilder = null)
{
configurationBuilder ??= new ConfigurationBuilder();
configurationBuilder
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddJsonFile("appsettings.json");
return configurationBuilder;
}
Essentially I've wrote a method that setups a ConfigurationBuilder and returns it, so that I can reuse the same configuration. This works in practice, but I build the same configuration twice.
Is there a simpler/more correct way (that works for HTTP workloads and non-HTTP workloads) to build and reuse the configuration before setting up the Host ?
You can clear the default sources added by CreateDefaultBuilder then add a pre-built IConfiguration with the AddConfiguration extension method.
public static void Main(string[] args)
{
//...
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddJsonFile("appsettings.json")
.Build();
//Do something useful with the configuration...
var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
builder.AddConfiguration(configuration);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
//...
}
The answer by UncleDave is certainly the best and most correct way to do this, but if you want to use the default configuration without recreating the logic yourself, it is not easy to get access to the IConfiguration and the IWebHostBuilder in the same place.
In order to do this, you can take advantage of the fact that the concrete Configuration is built before other services such as the web host are built. You can use ConfigureAppConfiguration() to access the config then use it to continue building the IWebHostBuilder.
For example:
public static async Task Main(string[] args)
{
var hostBuilder = Host.CreateDefaultBuilder(args);
var builder = hostBuilder
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
// make sure to put this at the end of this method
webBuilder.ConfigureAppConfiguration(configBuilder =>
{
// compile a temporary configuration instance
var configuration = configBuilder.Build();
// Check config values as you would normally
var myConfig = new MyConfig();
configuration.Bind("MyConfigSection", myConfig);
if (myConfig.UseSentryLogging)
{
// configure the web host based on config
webBuilder.UseSentry();
}
});
});
var host = builder.Build();
await host.RunWithTasksAsync();
}
If you are configuring other services which can affect where configuration is read from, then you may find you need to store a reference to the IWebHostBuilder and call ConfigureAppConfiguration on the IHostBuilder instead. Make sure you call ConfigureAppConfiguration last so that the other configuration setup can take place first and you access it correctly:
public static async Task Main(string[] args)
{
// we store the reference to the webHostBuilder so we can access it outside of ConfigureWebHost
IWebHostBuilder _webBuilder = null;
var hostBuilder = Host.CreateDefaultBuilder(args);
var builder = hostBuilder
.ConfigureWebHostDefaults(webBuilder =>
{
// store the builder for later
_webBuilder = webBuilder;
webBuilder.UseStartup<Startup>();
})
.ReadConfigFromSomewhereElse()
.ConfigureAppConfiguration(configBuilder =>
{
// compile a temporary configuration instance
var configuration = configBuilder.Build();
// Check config values as you would normally
var myConfig = new MyConfig();
configuration.Bind("MyConfigSection", myConfig);
if (myConfig.UseSentryLogging)
{
// configure the web host based on config
_webBuilder.UseSentry();
}
});
var host = builder.Build();
await host.RunWithTasksAsync();
}
Note this is a little bit of a hack and so may not work in all cases.
The right question is, do you really need a configuration for your configuration? You can use another file, and bind it directly with entity
configuration.GetSection("entity").Bind(entity);
If I didn't understand what you mean, all I know is that the setup you will need it for dependency injection, or you can't use it, so instead of using this, you can use Extensions for your service collection to do what you want with your configuration, and you get the setup in your container.
By default ConfigureAppConfiguration() will load appsettings.json according to the documentation Default builder settings.
Alternatively, if you can't access your configuration or you are getting System.NullReferenceException: 'Object reference not set to an instance ofan object.' error, it means the IHostBuilder can't see the json file in it base path.
Check or change the appsettings.json file properties as seen in the image below:
Set "Build Action" to "Content"
Set "Copy to Output Directory" to "Copy if newer"
I have an integration tests project that uses .UseSetting() in the test class, as follows:
public AccessTokenRetrieval() : base(nameof(AccessTokenRetrieval))
{
var connectionString = GetConnectionString();
var dbSettings = new DbSettings(connectionString);
_userGroupRepository = new UserGroupRepository(dbSettings);
_roleRepository = new RoleRepository(dbSettings);
_userRepository = new UserRepository(dbSettings);
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment("IntegrationTest")
.UseSetting("IntegrationTestConnString", dbSettings.IdentityServerConnectionString));
_handler = _server.CreateHandler();
_client = _server.CreateClient();
}
I would now like to retrieve that setting in the Startup.cs of my actual project. I attempted to do so using:
public void ConfigureIntegrationTestServices(IServiceCollection services)
{
var connectionString = Configuration.GetValue<string>("IntegrationTestConnString");
BuildIdentityServerTests(services, connectionString);
AddCoreServices(services, connectionString);
}
but that seems to return null.
What is the proper way to retrieve this setting?
You could just inject the IConfigurationRoot where you could just use a local config file in the test project. You'd lose the default environment-specific config overrides that are the default in the new project template, but I personally don't use those for configuration management anyway (preferring non-committed appsetting.local.json instead which can be unique to developers and to the CI server).
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true)
.AddInMemoryCollection(new Dictionary<string, string>
{
{"myConfig:setting1", "value1"},
{"myConfig:setting2", "value2"}
})
.Build();
var server = new TestServer(new WebHostBuilder()
.UseUrls("http://localhost:5001")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.ConfigureServices(services => services.AddSingleton(configuration)));
And for Startup:
public class Startup
{
public Startup(IConfigurationRoot configuration)
{
this.Configuration = configuration;
}
...
}
Edit:
While this does work, it also seems to break IOptionsSnapshot configuration injections from recognizing config file changes at run-time. I'm going to leave this answer, but will also keep digging for better way without injecting a custom service just to support test/fixture-specific config overrides.
Per-test setting
To pass a setting into a particular TestHost instance, you could use ConfigureServices call and override default setting registration.
First of all, register your default DbSettings instance in Startup.ConfigureServices method. It's mandatory to use TryAll call:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton(new DbSettings(...));
}
}
Register a mocked instance in WebHostBuilder.ConfigureServices call:
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddSingleton(new DbSettings(...));
}
);
When you try to resolve a DbSettings in the application code, you will get a mocked instance. Because WebHostBuilder.ConfigureServices is executed first and TryAdd call prevents to register a default instance.
This hint allows to replace any DI dependency you registered.
Global setting
To set an invariant (accross all tests) setting, set an process-scoped environment variable instead of UseSetting call:
Environment.SetEnvironmentVariable("foo", "bar");
var _server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
);
Then read it:
public void ConfigureServices(IServiceCollection services)
{
string setting1 = Configuration["foo"];
string setting2 = Environment.GetEnvironmentVariable("foo");
}
You need to add environment variable provider to read variables from Configuration:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
//this config provider is mandatory to read env vars from Configuration
.AddEnvironmentVariables();
Configuration = builder.Build();
}