Reputation: 27894
So I've been working with Dot Net Core. And I have IoC/DI working with "code er up".
And I've found the "By Environment" abilities to tweak.
The "By Environment" works great when you need tweaks...by....well...environment(s).
What is not working is different DI needs "by customer".
Let's say I have a product. And its goal is to talk to a customer's db, and each customer's db is complete different.
And I want to translate that data into a "standard".
(This is an illustration example, BTW).
I would write an interface : ICustomersDataAdapter
and I would write a
AcmeAdapter : ICustomersDataAdapter
MomAndPopsShopAdapter : ICustomersDataAdapter
(etc, etc)
With Microsoft Unity, this was trivial to swap out the different client adapters when I deployed the code (at a different location for each customer) using their configuration by xml.
This is not "by environment" because all my customer's have Development, Staging and Production environments.
Has anyone solved this type of IoC/DI with DI, and does not involve hacky stuff like
enc.IsEnvironment("AcmeProduction") where I mix/combine the the concerns of a different customer and environment. (<< BOO)
DotNetCore is so well thought out in so many different ways.
But this one, not sure how it was overlooked.
https://asp.net-hacker.rocks/2018/09/27/customizing-aspnetcore-03-dependency-injection.html
Maybe you want to configure the services in a configuration file outside the application, in an XML or JSON file instead in C# only
This is a common feature in various DI containers, but not yet supported in ASP.NET Core.
Upvotes: 1
Views: 718
Reputation: 27894
Ok.
I did find an answer....
https://autofac.readthedocs.io/en/latest/configuration/xml.html#id4
and a code snipplet (copied directly from the URL above) to help with searching (if the URL above ever dies)
using Autofac;
using Autofac.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
// Add the configuration to the ConfigurationBuilder.
var config = new ConfigurationBuilder();
// config.AddJsonFile comes from Microsoft.Extensions.Configuration.Json
// config.AddXmlFile comes from Microsoft.Extensions.Configuration.Xml
config.AddJsonFile("autofac.json");
// Register the ConfigurationModule with Autofac.
var module = new ConfigurationModule(config.Build());
var builder = new ContainerBuilder();
builder.RegisterModule(module);
In my specific case, I had to write some mini logic to find the specific "autofac.json" file(s) that are specifically for "MyCustomer". It wasn't hard, but in my solution, I had multiple .json files.
Below is MY code where I loop over the multiple ".json" files.
// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
IEnumerable<string> dependencyInjectionFileNames = GetAutofacDiFiles(/* not shown, but use an environment variable or other to drive which files you need for "my-customer" */);
/* now "register" each different json file */
foreach (string diFileName in dependencyInjectionFileNames)
{
ConfigurationBuilder currentLoopConfigurationBuilder = new ConfigurationBuilder();
currentLoopConfigurationBuilder.AddJsonFile(diFileName);
ConfigurationModule currentLoopConfigurationModule =
new(currentLoopConfigurationBuilder.Build());
builder.RegisterModule(currentLoopConfigurationModule);
}
Upvotes: 0
Reputation: 9235
Rather old question, however as for 2023 it seems there is no config-file based way to configure services for standard DI-container for cases like described in this question.
To address this, I've developed new library NReco.DependencyInjection (https://github.com/nreco/dependencyinjection/) that implements JSON-based IoC configuration as an extension. It can be used in addition to existing code-based definitions, only for configuration of components that may be changed without app recompilation, for example:
[
{"Type": "AcmeAdapter", "ServiceType": "ICustomersDataAdapter"}
]
(it is possible to have explicit injections by specifying "Constructor", "Properties")
Also it makes sense to use JSON-config to describe complex app configurations, instead of manual handling of IConfigurationSection loaded from appsettings.json.
Upvotes: 0
Reputation: 89
When I have multiple clients, for example, A, B and C, I will create different JSON files based on different agents and envs:
A.Development.json
, A.Staging.json
, A.Production.json
B.Development.json
, B.Staging.json
, B.Production.json
C.Development.json
, C.Staging.json
, C.Production.json
Inside each JSON file, it will looks like
{
"adapter" : "acme" // it can be "acme" or "momAndPopsShop", based on requirement
}
And I will create a parameter called 'client_id' in the Environment Variables
.
With the above configurations, inside the Program.cs
, I can know both 'client' and 'environment':
var clientId = Environment.GetEnvironmentVariable("client_id"); // clientId will be A,B or C
var env = hostingContext.HostingEnvironment;
var settingFile = $"{clientId}.{env}.json"; // the exact client & env
// config is IConfigurationBuilder
config.AddJsonFile("appsettings.json", true, true) // add default app settings
.AddJsonFile(settingFile, true, true); // add customized client settings
Till now, I have added the customized settings for the client & environment. Next, inside the DI part, I will read the IConfiguration and inject the corresponding service based on the settings.
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
....
services.ConfigureDatabase(Configuration);
}
...
}
public static void ConfigureAdapter(this IServiceCollection services, IConfiguration configuration)
{
var adapterType = configuration.GetSection("adapter").Value; // this value is read from A/B/C.env.json
switch (adapterType)
{
case "acme":
services.AddSingleton<ICustomersDataAdapter, AcmeAdapter>();
break;
case "momAndPopsShop":
services.AddSingleton<ICustomersDataAdapter, MomAndPopsShopAdapter>();
break;
default:
//log or throw error
break;
}
}
I am not very sure whether this is a good practice, but this is so far how I configure my app for different client and environment. Hope this method can help you find a better solution. (I think there will be a better way to do this)
Upvotes: 1