Started a new Console app in .NET 6 and am adding Dependency Injection. In the code below, how can I get access to the IConfiguration object to read a value from appsettings (after calling Build?
The configuration is available within the StoreFactory service, as its injected via the constructor, but if I want to read values from appsettings within the main section of code within program.cs, how can I get at it?
program.cs
var SvcBuilder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile("appsettings.json", optional: true);
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureServices((hostContext, services) =>
{
services.AddLogging(configure => configure.AddConsole())
.AddTransient<IStoreFactory, StoreCtxFactory>();
});
var host = SvcBuilder.Build();
The Host.CreateDefaultBuilder defines the behavior to discover the JSON configuration and expose it through the IConfiguration instance. From the host instance, you can ask the service provider for the IConfiguration instance and then ask it for values.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IHost host = Host.CreateDefaultBuilder(args).Build();
// Ask the service provider for the configuration abstraction.
IConfiguration config = host.Services.GetRequiredService<IConfiguration>();
// Get values from the config given their key and their target type.
var configValueInt = config.GetValue<int>("yourKeyName");
string configValueStr = config.GetValue<string>("yourKeyName");
For more information read the Docs.
Related
I am trying to create a modular environment to host Web APIs. I can break out various endpoints to separate projects, add them as a dependency to a "master" API project and call the endpoints successfully. Now I would like to change the program to not have to make each module a dependency in the project but rather have the "master" app look for files with a certain interface and load those dynamically at startup.
private static IEnumerable<IModule> DiscoverModules()
{
var type = typeof(IModule);
var types = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
.Select(x => Assembly.Load(AssemblyName.GetAssemblyName(x)))
.SelectMany(x => x.GetTypes().Where(x=>x.IsClass &&
x.IsAssignableTo(typeof(IModule))));
return types.Select(Activator.CreateInstance).Cast<IModule>();
}
I've gotten this to find the DLLs but it fails at the point of "Assembly.Load" claiming that it cannot find the file specified even though the file resides in the BaseDirectory.
Can dynamically loading libraries be accomplished without adding them as a dependency in the application?
The goal is to be able to load new endpoints without recompiling the "master" application.
Is there a better way to accomplish this?
try this for .Net6
public void LoadModule(IConfiguration configuration, IServiceCollection services)
{
AssemblyLoadContext? loadContext = null;
try
{
string tempLoadContextName = Guid.NewGuid().ToString();
loadContext = new AssemblyLoadContext(tempLoadContextName, true);
foreach (var dllFile in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.External.dll"))
{
Assembly assembly = loadContext.LoadFromAssemblyPath(dllFile);
var modules = assembly.DefinedTypes.Where(x => x.IsClass && x.IsAssignableTo(typeof(IServiceDependencyConfig)));
foreach (var module in modules)
{
var loadedModule = (IServiceDependencyConfig)Activator.CreateInstance(module)!;
loadedModule.AddServiceDependencyConfig(services, configuration);
}
}
}
finally
{
loadContext?.Unload();
}
}
where IServiceDependencyConfig is
public interface IServiceDependencyConfig
{
IServiceCollection AddServiceDependencyConfig(IServiceCollection services, IConfiguration configuration);
}
Basically I have to call a REST API from a .netcore console app which will return me some xml and then this console app will need to create a csv file from that xml.
I need to keep the REST API url and csv path in my appsettings.config.
I have created two keys for that.
Now I don't want to read these keys in my main method but I am building ConfigurationBuilder in my main method and therefore its available only in Main method.
Code in my Main method looks like below.
configuration = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddEnvironmentVariables()
.Build();
where configuration is static IConfiguartion configuration = null at Program.cs class level.
For now I have made this ConfigurationBuilder as a class level static variable , initilaizing it in Main then reading it elsewhere but not sure if its a good approach or there's some better way of doing this?
Any suggestions would be appreciated.
In my opinion you should use Dependency Injection like described in #Marco Luzzaras comment.
But if it is not possible in you scenario you can also access the same configuration by encapsule everything in a Singleton
public class ConfigurationProvider
{
private static IConfiguration _instance;
public static IConfiguration Instance
{
get
{
if(_instance == null)
{
_instance = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
return _instance;
}
}
}
... and use it everywhere you need it.
dotnet fiddle Example
Context
I am currently working on a .net Core 3.1 API that will replace an old .NET Framework and Delphi back-end. The API needs to supports globalization and localization to translate error messages and a few data values.
The localization is passed in through the route for example:
http://localhost/en-US/controller/action/params. So connected apps can quickly switch from localization.
The API has a Resources folder with the following files:
Resources:
- SharedResources.resx default and fallback language file nl-NL)
- SharedResources.en-US.resx
- SharedResources.de-DE.resx
In the Startup.cs I use installers to seperate concerns.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Extensions method that execute the install function on all classes that implement
// IInstaller interface
services.InstallServicesInAssembly(Configuration);
}
}
And I made a specific installer for the localization:
public class LocalizationInstaller : IInstaller
{
public void InstallServices(IServiceCollection services, IConfiguration configuration)
{
var localizationConfig = new LocalizationConfig();
configuration.GetSection(nameof(LocalizationConfig)).Bind(localizationConfig);
services.AddSingleton(localizationConfig);
services.AddLocalization(options => options.ResourcesPath = LocalizationConfig.ResourcePath);
services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = LocalizationConfig.GetDefaultRequestCulture();
options.SupportedCultures = LocalizationConfig.GetSupportedCultures();
options.SupportedUICultures = LocalizationConfig.GetSupportedCultures();
options.RequestCultureProviders = new[] { new RouteDataRequestCultureProvider { Options = options } };
});
services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint));
});
}
The Data of LocalizationConfig is from the appsettings.json file.
"LocalizationConfig": {
"ResourcePath": "Resources",
"SupportedCultures": [ "nl-NL", "en-US", "de-DE" ],
"DefaultRequestCulture": "nl-NL"
}
The problem
[HttpGet("{version}/{culture}/Index")]
public IActionResult Index()
{
var culture = $"CurrentCulture{CultureInfo.CurrentCulture.Name},CurrentUICulture{CultureInfo.CurrentUICulture.Name}";
var value = _localizer["HelloWorld"].Value;
return Ok(value);
}
When I make a request to the API with the culture being nl-NL or a random value it returns the default value in the SharedResources.resx like it is supposed to do.
For example:http://localhost/1/nl-NL/Controller/Index returns Hallo Wereld!.
But when I make a request with the culture being either en-US or de-DE it still returns the value from the SharedResources.rex file instead of the SharedResources.en-US.resx file or SharedResources.de-DE.resx file
while the culture from my variable var culture is being set to en-US or de-DE.
Notes
When I name my SharedResoures.en-US.resx to SharedResources.en.resx it does seem to work and the application finds the translation files. But this does not solve my issue that I want to specify the region code so that i can support both en-GB and en-US.
The Question
Why can the application find the file without the region code and not the file that does contain the region code?
Microsoft docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-3.1
I'm working with a C# class that reads from appsettings using
Environment.GetEnvironmentVariable("setting")
I want to make another console program (.net core 3.0) that reads settings from appsettings.json and loads them into environment variables.
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
IConfiguration config = builder
.Build();
var businessLogic = new BusinessLogic(); // uses GetEnvironmentVariable to get configuration
}
In my business logic class, the environment variables aren't getting pulled back correctly. What might be the problem?
appsettings.json is in this format:
{
"SETTING" : "setting value"
}
Environment.GetEnvironmentVariable("setting") will always try to read the data from an environment variable, not from your loaded config.
You need to change your BusinessLogic class to accept the config object you built and use that to access the settings. An example:
public class BusinessLogic
{
public BusinessLogic(IConfiguration config)
{
var value = config.GetValue<string>("SETTING");
}
}
In this example value will contain the correct value of SETTING if it was last set in appsettings.json or using an environment variable.
On fluent migrator and according to the documentation this is an example on how run migrations in process on .net core:
using System;
using System.Linq;
using FluentMigrator.Runner;
using FluentMigrator.Runner.Initialization;
using Microsoft.Extensions.DependencyInjection;
namespace test
{
class Program
{
static void Main(string[] args)
{
var serviceProvider = CreateServices();
// Put the database update into a scope to ensure
// that all resources will be disposed.
using (var scope = serviceProvider.CreateScope())
{
UpdateDatabase(scope.ServiceProvider);
}
}
/// <summary>
/// Configure the dependency injection services
/// </sumamry>
private static IServiceProvider CreateServices()
{
return new ServiceCollection()
// Add common FluentMigrator services
.AddFluentMigratorCore()
.ConfigureRunner(rb => rb
// Add SQLite support to FluentMigrator
.AddSQLite()
// Set the connection string
.WithGlobalConnectionString("Data Source=test.db")
// Define the assembly containing the migrations
.ScanIn(typeof(AddLogTable).Assembly).For.Migrations())
// Enable logging to console in the FluentMigrator way
.AddLogging(lb => lb.AddFluentMigratorConsole())
// Build the service provider
.BuildServiceProvider(false);
}
/// <summary>
/// Update the database
/// </sumamry>
private static void UpdateDatabase(IServiceProvider serviceProvider)
{
// Instantiate the runner
var runner = serviceProvider.GetRequiredService<IMigrationRunner>();
// Execute the migrations
runner.MigrateUp();
}
}
}
How do you specify a profile in case you want to load one?
I found it. Look at the last line.
return new ServiceCollection()
// Add common FluentMigrator services
.AddFluentMigratorCore()
.ConfigureRunner(rb => rb
// Add SQLite support to FluentMigrator
.AddSQLite()
// Set the connection string
.WithGlobalConnectionString("Data Source=test.db")
// Define the assembly containing the migrations
.ScanIn(typeof(AddLogTable).Assembly).For.Migrations())
.Configure<RunnerOptions>(cfg => { cfg.Profile = profile; })
This was wuite difficult to find in the documentation. Found it tucked away in this issue on gihub.
https://github.com/fluentmigrator/fluentmigrator/issues/886