How to inject connection string into external assembly (project) controller? - c#

My Web API is using other project for one controller. Service works fine. But I am struggling to inject connection string from main Web API project into controller in external project.
How could this be achieved?
public class MyExternalController : Controller
{
private string _connStr;
public MyExternalController(string connStr)
{
_connStr = connStr;
}
// actions here
}

As others said in the comments, for something like a controller, you should be injecting something concrete like a DbContext, not a connection string. However, for future reference your issue here is injecting a string. There's no way to register something in the DI container to satisfy a dependency like that. Instead, you should inject your configuration or a strongly-typed configuration class.
Injecting IConfigurationRoot is a bit of an anti-pattern, but for something like a connection string, it's fine:
public MyExternalController(IConfigurationRoot config)
{
_connStr = config.GetConnectionString("MyConnectionString");
}
For everything else, though, you should use strongly-typed configuration classes.
public class FooConfig
{
public string Bar { get; set; }
}
Then, in ConfigureServices:
services.Configure<FooConfig>(Configuration.GetSection("Foo"));
Which of course would correspond with some bit of config like:
{
"Foo": {
"Bar": "Baz"
}
}
Then, in your controller, for example:
public MyExternalController(IOptionsSnapshot<FooConfig> fooConfig)
{
_fooConfig = fooConfig.Value;
}

Related

NET Core dependency injection - resolve service or configuration based on dependent class

Assuming this use case:
You've got two classes X and Y that depends on a configuration of type Config
public class X
{
public X(IOptions<Config> config)
{
}
}
public class Y
{
public Y(IOptions<Config> config)
{
}
}
Now, you want to create each an instance of X and Y, but with different configurations. What would be the right way to register this?
From everything I read, the only way to solve this would be by adding some sort of "naming" for the different configuration instances and resolve them via a custom resolver:
public delegate Config ServiceResolver(string key);
services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
switch (key)
{
case "A":
return ... (whatever to get the first config instance);
case "B":
return ... (whatever to get the second config instance);
default:
throw new KeyNotFoundException();
}
});
However, this means that the implementation of each X and Y must know about details about how to get the configurations:
They must know the correct name (A or B) and
they must know the ConfigResolver type, which is only an implementation detail/helper class for the sake of dependency injection.
This problem hits even harder if you need to go through several stages of dependencies, like
Config (A) Config (B)
| |
v v
Service Service
| |
v v
X Y
My feeling is, there should be a better way to solve this.
Like some form of receipent dependent service factory:
Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => {
services.Configure<Config>(context.Configuration.GetSection("ConfigA")).For<X>();
services.Configure<Config>(context.Configuration.GetSection("ConfigB")).For<Y>();
});
and maybe
Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => {
services.AddTransient<Service>((services, receiverType) => {
if(receiverType == typeof(X)) {
... resolve a service instance;
}
else {
... resolve some other service instance;
}
});
});
So, is there just some feature I missed until now? Is my understanding of the situation totaly misguided? Or is this really a feature that should be, but has not been added until now?
EDIT:
To make my point clearer: Just assume that X and Y are classes of a third-party library. Their constructors signature cannot be changed by you, as you don't have access to the source code.
So, how would you set this up in a way that you can get each an instance of X with ConfigA and an instance of Y with ConfigB?
Another EDIT 2023-01-02:
Happy new year everyone :)
Seems I have to describe a bit better what's my problem. This is not constrained to IOptions/configurations, but more a general question about where to decide about which service to inject and how it is configured.
Assume I have two a congress location with 2 stages. I call them "bigStage" and "smallStage", but in the end they've got the same implementation. I also got two speakers invited, called "loadSpeaker" and "quietSpeaker", but at this moment in time I don't know which one will speak on which of the two stages.
So I decide I've got this setup:
class Stage {
public Stage(string name, ISpeaker speaker) {
...
}
}
class Speaker: ISpeaker {
public Speaker(string name) {
...
}
}
Now, at the latest time possible, I want to compose my final setup so that I've got 2 Stages (called bigStage and smallStage) and their assigned Speakers (loudSpeaker on bigStage and quietSpeaker on smallStage). This composition/assignment should completely happen in my composition root, so that no code changes have to happen in the rest of my code. How can I do that?
I suggest to use a factory for your Service:
class X {
private readonly Service _service;
public X(ServiceFactory serviceFactory) {
_service = serviceFactory.Create<X>();
}
}
class Service {
private readonly Config _config;
public Service(Config config) { _config = config; }
}
class ServiceFactory {
private readonly IConfiguration _configuration;
/* other Service dependencies would also be injected here */
public ServiceFactory(IConfiguration configuration, /* Service dependencies */) {
_configuration = configuration;
...
}
public Service Create<T>() {
return Create(typeof(T));
}
public Service Create(Type type) {
var configName = switch typeof(T) {
X => "ConfigX",
Y => "ConfigY",
default => throw new Exception()
};
var config = _configuration.GetSection(configName).Get<Config>();
return new Service(config, /* other dependencies */);
}
}
The switch statement can be replaced with a Dictionary<Type, string> or Dictionary<string, string> if you would want to export this dictionary to IConfiguration.
Getting the Config can be also cached for performance (don't forget the thread safety)
So the "trick" to all of this is... you have to piggy back onto ~something to make a decision on which one IMySomething . when you register multiple IMySomething(s).
The factory above where you switch/case on the object.TYPE....is one way.
But it is "fragile", IMHO. Or at the very last, violates the Open/Closed principle of SOLID, as you have to keep editing the code to add a new case-statement.
So I also think you want a Factory.......BUT I do not like "hard coding" the values of the switch/case statements.
So if you follow my IShipper example:
Using a Strategy and Factory Pattern with Dependency Injection
I think you want to create a
IShipperFactory
and inject the IEnumerable of "IShipper"(s).
..
Then you will use your IShipperFactory... when registering your things that need an IShipper.
This does cause a small "ripple" because you need access to the IShipperFactory....to do (later) IoC registrations.
But it would be "clean" and have good separations of concerns.
Let me pseudo code it.
public interface IShipper (from other article)
3 concretes (Usps, FedEx, Ups)
public interface IShipperFactory()
public IShipper GetAnIShipper(string key)
..
public class ShipperFactoryConcrete
(from other article, inject multiple IShippers here)
public IShipper GetAnIShipper(string key)
// look through the injected IShippers to find a match, or else throw exception.
.....
public interface IOrderProcessor
..
public class WestCoastOrderProcessor : IOrderProcessor
/* c-stor */
public WestCoastOrderProcessor(IShipper aSingleShipper)
public class EastCoastOrderProcessor : IOrderProcessor
/* c-stor */
public WestCoastOrderProcessor(IShipper aSingleShipper)
........
Ok, so we decide at compile-time, we want to define the "best" IShipper for the EastCoastOrderProcessor and WestCoastOrderProcessor. (making up some kind of example here)
So need need to IoC register.
from the other article:
cont.RegisterType<IShipper, FedExShipper>(FedExShipper.FriendlyName);
cont.RegisterType<IShipper, UspsShipper>(UspsShipper.FriendlyName);
cont.RegisterType<IShipper, UpsShipper>(UpsShipper.FriendlyName);
now it gets a little "off beaten path".
See:
https://stackoverflow.com/a/53885374/214977
and
// so this is a cart-horse situation, where we need something from the IoC container.... to complete the IoC registrations.
IShipperFactory sf = services.GetRequiredService<IShipperFactory>(); // see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0#resolve-a-service-at-app-start-up
.. and now we IoC register...but we specify specific values for the constructor. please see the SOF (214977), for syntax-sugar hints. the below is definately pseduo code.....
_serviceCollection.AddSingleton<IOrderProcesor>(x =>
ActivatorUtilities.CreateInstance<EastCoastOrderProcessor>(x, sf.GetAnIShipper(FedExShipper.ShipperName));
);
_serviceCollection.AddSingleton<IOrderProcesor>(x =>
ActivatorUtilities.CreateInstance<WestCoastOrderProcessor>(x, sf.GetAnIShipper(UspsShipper.ShipperName));
);
APPEND:ONE:
Another "trick" .. if you have a code base that you cannot change is.
The "proxy design pattern":
The Proxy design pattern provides a surrogate or placeholder for
another object to control access to it.
https://www.dofactory.com/net/proxy-design-pattern
public EastCoastOrderProcessorProxy
private readonly ThirdPartyOrderProcessor innerThirdPartyOrderProcessor;
public EastCoastOrderProcessor(ThirdPartyOrderProcessor innerThirdPartyOrderProcessor)
{
this.innerThirdPartyOrderProcessor = innerThirdPartyOrderProcessor;
}
..
public WestCoastOrderProcessorProxy
private readonly ThirdPartyOrderProcessor innerThirdPartyOrderProcessor;
public EastCoastOrderProcessor(ThirdPartyOrderProcessor innerThirdPartyOrderProcessor)
{
this.innerThirdPartyOrderProcessor = innerThirdPartyOrderProcessor;
}
So while you cannot change the ThirdPartyOrderProcessor, you can write 1:N wrapper-proxies around it.
The simplest solution I can think of, without using named options inside of your service classes, is moving the selection of the configuration object from the class constructor to the composition root of the application.
This way, your service class simply receives a configuration object as a constructor parameter and it is not aware of the underlying configuration infrastructure.
The composition root, which is in charge of composing the objects which make your application, do know about the configuration infrastructure and picks the right configuration object for your services.
In order to implement this pattern, you need to define an option class as the first step. This option class is needed in order to leverage the options pattern support offered by ASP.NET core. You will only use this class at the composition root level.
public sealed class LayoutOptions
{
public const string Layout = "Layout";
public const string Basic = "Basic";
public const string Complex = "Complex";
public string Name { get; set; } = default!;
public string Color { get; set; } = default!;
public int NumberOfColumns { get; set; }
}
Then you need to define a class which represents the configuration object for your services. This is basically a strongly typed configuration object used to configure your services. This object is built strating from the options class, notice that you don't need to make it identical to the options class itself.
public sealed class LayoutConfiguration
{
public string Name { get; }
public string Color { get; }
public LayoutConfiguration(string name, string color)
{
Name = name;
Color = color;
}
}
Now you need to define your service classes. These types are configured by using the LayoutConfiguration configuration class. Each service class will be properly configured by the composition root of the application, by using the proper named options.
public interface ILayoutService
{
string GetLayoutDescription();
}
public sealed class BasicLayoutService : ILayoutService
{
private readonly LayoutConfiguration _config;
public BasicLayoutService(LayoutConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}
public string GetLayoutDescription() =>
$"Basic layout description. Name: '{_config.Name}' Color: '{_config.Color}'";
}
public sealed class ComplexLayoutService : ILayoutService
{
private readonly LayoutConfiguration _config;
public ComplexLayoutService(LayoutConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}
public string GetLayoutDescription() =>
$"Complex layout description. Name: '{_config.Name}' Color: '{_config.Color}'";
}
You can also defined a couple of controllers, that you can use to test this implementation and be user that your services are wired-up correctly by the composition root of the application:
[ApiController]
[Route("[controller]")]
public sealed class BasicLayoutController : ControllerBase
{
private readonly BasicLayoutService _basicLayoutService;
public BasicLayoutController(BasicLayoutService basicLayoutService)
{
_basicLayoutService = basicLayoutService ?? throw new ArgumentNullException(nameof(basicLayoutService));
}
[HttpGet("description")]
public string GetDescription() => _basicLayoutService.GetLayoutDescription();
}
[ApiController]
[Route("[controller]")]
public sealed class ComplexLayoutController : ControllerBase
{
private readonly ComplexLayoutService _complexLayoutService;
public ComplexLayoutController(ComplexLayoutService complexLayoutService)
{
_complexLayoutService = complexLayoutService ?? throw new ArgumentNullException(nameof(complexLayoutService));
}
[HttpGet("description")]
public string GetDescription() => _complexLayoutService.GetLayoutDescription();
}
This is the most important part. Put this registration code inside the Program.cs class (which is the composition root for an ASP.NET core 6 application):
// Configure named options
builder.Services.Configure<LayoutOptions>(
LayoutOptions.Basic,
builder.Configuration.GetSection($"{LayoutOptions.Layout}:{LayoutOptions.Basic}")
);
builder.Services.Configure<LayoutOptions>(
LayoutOptions.Complex,
builder.Configuration.GetSection($"{LayoutOptions.Layout}:{LayoutOptions.Complex}")
);
// Register the BasicLayoutService by picking the right configuration
builder
.Services
.AddScoped(serviceProvider =>
{
// Get named options
var layoutOptions = serviceProvider.GetRequiredService<IOptionsSnapshot<LayoutOptions>>();
var basicLayoutOptions = layoutOptions.Get(LayoutOptions.Basic);
// Create strongly typed configuration object from named options
var configuration = new LayoutConfiguration(
basicLayoutOptions.Name,
basicLayoutOptions.Color);
// Creates new instance of BasicLayoutService using the service provider and the configuration object
return ActivatorUtilities.CreateInstance<BasicLayoutService>(
serviceProvider,
configuration);
});
// Register the ComplexLayoutService by picking the right configuration
builder
.Services
.AddScoped(serviceProvider =>
{
// Get named options
var layoutOptions = serviceProvider.GetRequiredService<IOptionsSnapshot<LayoutOptions>>();
var complexLayoutOptions = layoutOptions.Get(LayoutOptions.Complex);
// Create strongly typed configuration object from named options
var configuration = new LayoutConfiguration(
complexLayoutOptions.Name,
complexLayoutOptions.Color);
// Creates new instance of ComplexLayoutService using the service provider and the configuration object
return ActivatorUtilities.CreateInstance<ComplexLayoutService>(
serviceProvider,
configuration);
});
You can now test this implementation. As an example, you can set the following configuration in appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Layout": {
"Basic": {
"Name": "Basic Layout",
"Color": "red",
"NumberOfColumns": 2
},
"Complex": {
"Name": "Complex Layout",
"Color": "blue",
"NumberOfColumns": 3
}
}
}
If you run this application and you issue a GET request to /BasicLayout/description, you ge the following response:
Basic layout description. Name: 'Basic Layout' Color: 'red'
If you issue a GET request to /ComplexLayout/description the response you get is:
Complex layout description. Name: 'Complex Layout' Color: 'blue'
A final note on the service lifetime for BasicLayoutService and ComplexLayoutService. In my example I decided to register them as scoped services, because you may want to recompute the configuration object for them (LayoutConfiguration) for each incoming request. This is useful if your configuration may change over time. If this is not the case, you can safely register them as singleton services. That's up to you and depends on your requirements.

Injection in a referenced class library?

I have a ASP.NET Core 2.1 website that references a class library(DAL). To access the connectionstring from the appsettings.json(ASP.NET project) I need inject the configuration somehow. I have created a class in the class Library that looks like this :
public class Helper
{
IConfiguration Configuration;
public Helper(IConfiguration configuration)
{
Configuration = configuration;
}
public string GetConnectionString(string name)
{
return Configuration.GetConnectionString("DefaultConnection");
}
}
The injection pattern do however not pick this up so it demands a IConfigration to create the class.
How do I access the appsettings.json from the class library?
Your class library should not know or care how you're handling configuration in your app. All your Helper class needs is a connection string, so that is what you should inject into it. How that string is provided is an implementation detail that's part of your application domain.
public class Helper
{
public Helper(string connectionString)
{
// do something with connectionString
}
}
Then, in your app:
services.AddScoped(p =>
new Helper(Configuration.GetConnectionString("DefaultConnection")));

Dependency injection : static parameters and services

I have this service that will be injected to some other controllers.
It needs a service, and a connection string that is taken from a configuration file.
public class MyService : IMyService
{
public MyService(IService1 service1, IService2 service2, string connectionString){
//...
}
}
I would like IService1 and IService2 to be injected, but connectionString to be specified manually. I can't get my head around a way to work this out, the examples I saw were either massively complex, or just not what I wanted to achieve.
public void ConfigureServices(IServiceCollection services)
{
var cfg = new MyConfiguration();
Configuration.Bind("config", cfg);
var connectionString = cfg["myConnectionString"];
services.AddSingleton<IMyService, MyService>(/*what can I do here?*/)
}
Can this be achieved simply ?
You have access to the service provider within the factory delegate.
Resolve the other dependencies and inject the static variable when initializing the service.
//...
var connectionString = cfg["myConnectionString"];
services.AddSingleton<IMyService>(_ =>
new MyService(_.GetService<IService1>(), _.GetService<IService2>(), connectionString));
In the above example _ in the factory delegate is a IServiceProvider, and GetService<T> extension method is used to resolve the other services provided they are also registered with the service collection.
The factory delegate will be invoked the first time IMyService is requested.
As an alternative, referencing Options pattern in ASP.NET Core
And assuming for example a settings.json file
{
"myConnectionString": "value1_from_json",
}
consider using the IOptions<T> provided by the configuration extension.
create a class to store the desired configuration.
public class MyConnections {
public string MyConnectionString { get; set; }
}
refactor the class to depend on IOption<MyConnections>
public class MyService : IMyService {
private string connectionString;
public MyService(IService1 service1, IService2 service2, IOptions<MyConnections> options){
connectionString = options.Value.MyConnectionString;
//...
}
//...
}
and configure it on start up
//...
services.Configure<MyConnections>(Configuration);
services.AddSingleton<IMyService, MyService>();
You could do it like Nkosi suggests and use a factory that creates the service. This however has the drawback that this is very explicit and will require you to always adjust the object creation whenever your constructor signature changes (for example when you need different dependencies).
A more proper solution in the ASP.NET Core world would be to use the options pattern. What you do is basically instead of requiring a connection string to be passed to the constructor, you create a new configuration type that configures your service. You can then configure this object using the options pattern.
This would look like this:
public class MyService : IMyService
{
public MyService(IService1 service1, IService2 service2, IOptions<MyServiceOptions> serviceOptions)
{
var connectionString = serviceOptions.Value.ConnectionString;
//...
}
}
public class MyServiceOptions
{
public string ConnectionString
{ get; set; }
}
And then in your startup, just register your type and configure the options:
services.AddSingleton<IMyService, MyService>();
services.Configure<MyServiceOptions>(options => {
options.ConnectionString = "connection string";
});
If you place the configuration for your connection string into a separate configuration section, you could even do this:
services.Configure<MyServiceOption>(configuration.GetSection("MyService"));
Assuming your configuration looks like this:
{
"MyService": {
"ConnectionString": "…"
},
// …
}

Application Settings in custom class ASP.Net 5 MVC 6

Playing around with ASP.Net 5 MVC. Seen this question jumping around but not an full answer. What I want to do is have a helper class that is able to access the AppSettings. I can access it in the controller and the view but haven't figured out how to access it on my own custom class. Have startup configured like so.
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// 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 http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}
.................
.................
So in your config.json file, suppose you have following settings
{
"smtp": {
"SenderEmail": "a#b.com",
"SenderFrom": "Test User"
}
}
Then in your ConfigureServices method you need to do something like that
services.Configure<SmtpEmailSetting>(Configuration.GetSection("smtp"));
This is your SmtpEmailSetting looks like
public class SmtpEmailSetting
{
public string SenderEmail { get; set; }
public string SenderFrom { get; set; }
}
and this is how you access your settings in any service or controller
public class SendEmailService
{
private readonly SmtpEmailSetting _smtpEmailSetting;
public SendEmailService(IOptions<SmtpEmailSetting> smtpOptions )
{
_smtpEmailSetting = smtpOptions.Value;
}
public void SendEmail()
{
var fromEmail = _smtpEmailSetting.SenderEmail;
var displayName = _smtpEmailSetting.SenderFrom;
}
}
So basically you use your settings or options (whatever you prefer to call) should be used in constructor as a generic type parameter of IOptions<> class. Hope it helps
In order to access your AppSettings properties in your custom class, make configuration as a static instance such as:
public static IConfigurationRoot Configuration { get; set; }
and make use of your AppSettings any where in your application (for connectionstring example) as:
var connectionString = Startup.Configuration["Data:DefaultConnection:ConnectionString"];
Just to add to adeel41's answer, this is correct and works great, but for myself, I didn't want to drag around an IOption object when using dependency injection.
So I prefer to do something like
services.AddSingleton<ISmtpEmailSettings>(Configuration.GetSection("SmtpSettings").Get<SmtpEmailSettings>());
Most importantly is the Get syntax added to GetSection to deserialize your JSON to an object.
At the time of RC1, I took inspiration from this post by Rick Strahl, which worked great. That is very similar to other approaches already proposed.
This answer is just to update with my findings as of RTM release. It seems like Configuration.GetSection(string topLevelKey) does not work anymore, at least for me it always returns null (even if configuration sources are set correctly).
After some search, this other SO thread pointed me in the right direction, by using:
// create empty config object
var smtpEmailSetting = new SmtpEmailSetting();
// fill it from configuration section
Configuration.GetSection("smtp").Bind(smtpEmailSetting);
// use it, e.g. by registering it into DI
services.Configure<SmtpEmailSetting>(smtpEmailSetting);
HTH
If you need it in your own class, it's probably right to pass it into the constructor of that class, or as a parameter. Eg;
public class Notifications
{
public Notifications(AppSettings settings) {
this.settings = settings;
}
public void SendEmail(string subject, string body) {
SmptClient.Send(subject, body, settings["email address"]);
}
}
So typically, you'd pass it through from your controller.
This avoids a global variable, which is always a good thing, I think.

How do I handle a configuration class that are loaded at runtime with dependency injection?

I'm currently trying to work with dependency injection and so far I love. But it's one thing I can't really get my head around and where my current solution just seems wrong.
I'm working with WPF, MVVM and many of the classes I inject need an instance of a project configuration class that isn't initialized until the user create or open a new project in the application.
So my current solution is to have a "ConfigurationHandler" with load/save method and a property that hold an instance of the configuration class after it's loaded. I inject ConfigurationHandler to the others classes and then they can access the configuration after it's loaded. But it seems weird to let classes that never should save/load configuration handle the whole "ConfigurationHandler" and 100% they would just use it to access the configuration instance likt this:
var configuration = configurationHandler.Configuration;
Another problem is that if they try to access the configuration before it's loaded they will get exception (should not really happen as you can't do anything before a project is created/loaded, but still).
But the only other solution I can think of is to use "intialize" methods after a project is created/open but that seems just as bad.
So how do you usually handle cases like this?
Edit: Should add that this configuration class handle information like project path, project name, etc so have nothing to do with the dependency injection itself.
If your configuration is static (read: It's only read during startup of your application, such as from project.json or Web.Config), you can also set it during app startup/the composition root.
The new ASP.NET 5 uses it heavily and it works very well. Basically you will have an IConfiguration<T> interface and a POCO class, which you set up during the app startup and can resolve/inject it into your services.
public interface IConfiguration<T> where T : class
{
T Configuration { get; }
}
And it's default implementation
public interface DefaultConfiguration<T> where T : class
{
private readonly T configuration;
public T Configuration {
return configuration;
}
public DefaultConfiguration<T>(T config)
{
this.configuration = this.configuration;
}
}
And your POCO class
public class AppConfiguration
{
public string OneOption { get; set; }
public string OtherOption { get; set; }
}
In your composition root, you would then register it, like
// read Web.Config
Configuration rootWebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(null);
container.AddSingleton<IConfiguration<AppConfiguration>>(new DefaultConfiguration<AppConfiguration>(
new AppConfiguration
{
OneOption = rootWebConfig.AppSettings.Settings["oneSetting"],
OtherOption = rootWebConfig.AppSettings.Settings["otherSetting"],
})
);
And finally, all you have to declare in your services is
public class MyService : IMyService
{
public MyService(IUserRepository, IConfiguration<AppConfiguration> appConfig)
{
...
if(appConfig.OneOption=="someValue") {
// do something
};
}
}
Finally you can make this a bit easier to configure, if you write an extension method like
public static class MyContainerExtension
{
public static void Configure<T>(this IMyContainer container, Action<T> config) where T : class, new()
{
var t = new T();
config(t);
container.AddSingelton<IConfiguration<T>>(t);
}
}
Then all you need to do is
container.Configure<AppConfiguration>(
config =>
{
config.OneOption = rootWebConfig.AppSettings.Settings["oneSetting"],
config.OtherOption = rootWebConfig.AppSettings.Settings["otherSetting"],
})
);
to set it up
Instead of Constructor Injection, consider using an Ambient Context approach.
The last type of DI we’ll discuss is making dependencies available
through a static accessor. It is also called injection through the
ambient context. It is used when implementing cross-cutting concerns.
This is a good option if the classes that need access to your configuration are of different types in different layers or libraries - i.e. is a true cross-cutting concern.
(Quote source)
Example, based on the classic Time Provider one from [Dependency Injection in .NET][2]
abstract class CustomConfiguration
{
//current dependency stored in static field
private static CustomConfiguration current;
//static property which gives access to dependency
public static CustomConfiguration Current
{
get
{
if (current == null)
{
//Ambient Context can't return null, so we assign a Local Default
current = new DefaultCustomConfiguration();
}
return current;
}
set
{
//allows to set different implementation of abstraction than Local Default
current = (value == null) ? new DefaultCustomConfiguration() : value;
}
}
//service which should be override by subclass
public virtual string SomeSetting { get; }
}
//Local Default
class DefaultCustomConfiguration : CustomConfiguration
{
public override string SomeSetting
{
get { return "setting"; }
}
}
Usage
CustomConfiguration.Current.SomeSetting;
There are other DI Patterns that could be used, but require changes to the class that need it. If Configuration is a cross cutting concern Ambient Context could be the best fit.
Constructor Injection Example
public SomeClass(IConfiguration config)
{
}
Property Injection
public SomeClass()
{
IConfiguration configuration { get; set; }
}
Method Injection
public SomeClass()
{
public void DoSomethingNeedingConfiguation(IConfiguration config)
{
}
}
There is also Service Locator, but Service Locator is (IMO) an anti-pattern.

Categories

Resources