I would like dynamically load and register services in my application. To do that I need to be able to load configuration files from different projects in solution and merge values from them into single json array. Unfortunately by default in ASP.Net Core configuration overrides values.
I register files with following code (part of Program.cs file):
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((webHostBuilderContext, configurationbuilder) =>
{
var env = webHostBuilderContext.HostingEnvironment;
configurationbuilder.SetBasePath(env.ContentRootPath);
configurationbuilder.AddJsonFile("appsettings.json", false, true);
var path = Path.Combine(env.ContentRootPath, "App_Config\\Include");
foreach(var file in Directory.EnumerateFiles(path, "*.json",SearchOption.AllDirectories))
{
configurationbuilder.AddJsonFile(file, false, true);
}
configurationbuilder.AddEnvironmentVariables();
})
.UseStartup<Startup>();
The code searches for all files with *.json
extension inside App_Config\Include
directory and adds all of them to the configuration builder.
Structure of the files look following way:
{
"ServicesConfiguration": {
"Services": [
{
"AssemblyName": "ParsingEngine.ServicesConfigurator, ParsingEngine"
}
]
}
}
As you can see I have got main section ServicesConfiguration
then Services
array with objects which have one attribute AssemblyName
.
To read those values I use ServicesConfiguration
class with list:
public class ServicesConfiguration
{
public List<ServiceAssembly> Services { get; set; }
}
And that list uses ServiceAssembly
class:
public class ServiceAssembly
{
public string AssemblyName { get; set; }
}
To load that configuration I use IOptions
at constructor level (DI):
Microsoft.Extensions.Options.IOptions<ServicesConfiguration> servicesConfiguration,
And configuration seems to be loaded - but values from files are not merged but overridden by last found file.
Any ideas how to fix that?
var myList = new List<int>(); myList. AddRange(arr1); myList. AddRange(arr2); Use the AddRange() method the arrays into the newly created list.
Of course, we can add and use multiple appsettings. json files in ASP.NET Core project.
To read configuration values in ASP.NET Core, you need to follow the Options pattern. To implement it, define a configuration class matching the values you want to read from the appsetttings. json file and use the default dependency container to inject the read values.
You could add the IConfiguration instance to the service collection as a singleton object in ConfigureServices : public void ConfigureServices(IServiceCollection service) { services. AddSingleton<IConfiguration>(Configuration); //... }
So you have an idea on what I meant in my comments here's a potential answer
Since you have to load different "config" files from different projects and apply some merging logic to them, I would just avoid using the "default" configuration system to load the JSON
files into the app. Instead, I would just do it myself. So:
ServicesConfiguration
as a Singleton
Program.cs
to load the custom JSON
filesHere's how you could do it:
ServicesRootConfiguration
(new class, to be able to deserialize the json)
public class ServicesRootConfiguration
{
public ServicesConfiguration ServicesConfiguration { get; set; }
}
Startup.cs
public class Startup
{
private readonly IHostingEnvironment _hostingEnvironment;
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
_hostingEnvironment = env;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// other configuration omitted for brevity
// build your custom configuration from json files
var myCustomConfig = BuildCustomConfiguration(_hostingEnvironment);
// Register the configuration as a Singleton
services.AddSingleton(myCustomConfig);
}
private static ServicesConfiguration BuildCustomConfiguration(IHostingEnvironment env)
{
var allConfigs = new List<ServicesRootConfiguration>();
var path = Path.Combine(env.ContentRootPath, "App_Config");
foreach (var file in Directory.EnumerateFiles(path, "*.json", SearchOption.AllDirectories))
{
var config = JsonConvert.DeserializeObject<ServicesRootConfiguration>(File.ReadAllText(file));
allConfigs.Add(config);
}
// do your logic to "merge" the each config into a single ServicesConfiguration
// here I simply select the AssemblyName from all files.
var mergedConfig = new ServicesConfiguration
{
Services = allConfigs.SelectMany(c => c.ServicesConfiguration.Services).ToList()
};
return mergedConfig;
}
}
Then in your Controller
just normally get the instance by DI.
public class HomeController : Controller
{
private readonly ServicesConfiguration _config;
public HomeController(ServicesConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}
}
With this approach, you ended up with the same behavior as you would get from normally registering the IOptions
. But, you avoid having a dependency on it and having to use the uggly .Value
(urgh). Even better, you could register it as an Interface so it makes your life easier during testing/mocking.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With