I want to have a component register other components in the registry as / after it's constructed. Let's say I have the following components:
interface IConfiguration
{
string SourceDirectory { get; }
string TargetDirectory { get; }
// other primitive-typed configuration parameters
}
class FileConfiguration : IConfiguration
{
// read parameters from some config file
}
class SourceDirectoryWrapper
{
public byte[] ReadFile(string filename)
{
// read a file from the source directory
}
public string Directory { get; set; }
}
class TargetDirectoryWrapper
{
public byte[] WriteFile(string filename)
{
// write a file into the source directory
}
public string Directory { get; set; }
}
class DirectoryWrapperFactory
{
public DirectoryWrapperFactory(IConfiguration config)
{
var source = new SourceDirectoryWrapper {
Directory = config.SourceDirectory
};
var target = new TargetDirectoryWrapper {
Directory = config.SourceDirectory
};
}
}
The components FileConfiguration and DirectoryWrapperFactory can be registered as is usual.
However, what I'd like to accomplish is to somehow "outject" the source and target objects created in DirectoryWrapperFactory. The basic idea is that different environments might require different configuration providers. (And even if not, I think it's a good idea to put reading configuration parameters into a separate component.)
I'd also like to have SourceDirectoryWrapper and TargetDirectoryWrapper managed in the IoC container. In my case, mainly for convenience – I have an EventSource implementation that I need everywhere, so I inject it using property autowiring. Every object not in the IoC container needs to have it passed explicitly, which kind of bugs me.
So: is this possible with AutoFac? If so, how? I poked at the lifecycle events but most don't allow access to the registry after an object is built.
I don't quite understand why DirectoryWrapperFactory needs to exist. You could just register SourceDirectoryWrapper and TargetDirectoryWrapper directly as part of normal wireup:
builder.Register(c => new SourceDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
builder.Register(c => new TargetDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
Related
I'm using the Options pattern to configure my ASP.net Core 3.1 web app.
There are two options classes:
public class SystemOptions
{
public string RootPath { get; set; }
}
public class ModuleOptions
{
public string SubPath { get; set; }
// this should become something like RootPath + SubPath
public string FullPath { get; }
}
And the associated appsettings.json
{
"SystemOptions": {
"RootPath": "\\webdav"
},
"ModuleOptions": {
"SubPath": "\subdirformodule"
}
}
And in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SystemOptions>(configuration.GetSection("SystemOptions"));
services.Configure<ModuleOptions>(configuration.GetSection("ModuleOptions"));
}
Now I would like to initialize the FullPath in ModuleOptions once during app startup.
Therefore I need access to the SystemOptions.RootPath from within the ModuleOptions.
Is this possible?
I tried the following:
I added an InitializeFullPath() method to the ModuleOptions:
public string InitializeFullPath(string basePath)
{
// concat basePath and SubPath and return
... return fullPath;
}
and tried to use this in ConfigureServices:
services.Configure<SystemOptions>(configuration.GetSection("SystemOptions"));
services.AddOptions<ModuleOptions>()
.Configure<SystemOptions>((s, m) => m.FullPath = m.InitializeFullPath(s.RootPath));
But all I get is:
"No service for type '...SystemOptions' has been registered."
later on when Startup.Configure() is executed.
(And by the time this error occured, the InitializeFullPath method has not been executed at all - a breakpoint set there was not hit.)
So I have two questions:
how can I use the content of one option object during initialization of the second option object?
When will the delegate that you can specify in Configure() be executed?
I am going to answer your second question first. The configuration delegate is invoked the first time the Value property of the IOptions<YourOptions> is invoked. This interface is registered as a singleton so it's a one-time only thing. For IOptionsMonitor/IOptionsSnapshot they are similarly invoked on every new instance of the options.
Now to your first question... You were close! This should work:
services.AddOptions<ModuleOptions>()
.Configure<IOptions<SystemOptions>>(
(mod, sys) => mod.FullPath = mod.InitializeFullPath(sys.Value.RootPath)
);
Note that we are using IOptions<SystemOptions> and .Value. The Configure method that is chained to AddOptions is not the same as the one directly on the service collection; the generic arguments are the dependent service types and the first parameter is the options type from AddOptions. So that means that you reversed the arguments to the delegate (the option being configured is the first parameter).
Another...option is to use the IConfigureOptions interface. I typically go this route and don't use the form you have shown, even for "simple" dependent configuration:
public ModuleOptionsConfigurator : IConfigureOptions<ModuleOptions>
{
private readonly SystemOptions _sys;
public ModuleOptionsConfigurator(IOptions<SystemOptions> opts)
=> _sys = opts.Value;
public void Configure(ModuleOptions mod)
{
mod.FullPath = mod.InitializeFullPath(_sys.RootPath);
}
}
Which you then register with DI like so:
services.Configure<SystemOptions>(configuration.GetSection("SystemOptions"));
services.Configure<ModuleOptions>(configuration.GetSection("ModuleOptions"))
// register the configurator
services.ConfigureOptions<ModuleOptionsConfigurator>();
This allows you to encapsulate any sort of configurarion logic into a class. You can take zero dependencies up to however many you need.
The IPostConfigureOptions<> interface works similarly, but will run after all other Configure callbacks and IConfigureOptions<> implementations (and allows you to act differently for named options). Based on your description, this may be the better interface:
public ModuleOptionsPostConfigurator : IPostConfigureOptions<ModuleOptions>
{
private readonly SystemOptions _sys;
public ModuleOptionsPostConfigurator(IOptions<SystemOptions> opts)
=> _sys = opts.Value;
public void PostConfigure(string name, ModuleOptions mod)
{
mod.FullPath = mod.InitializeFullPath(_sys.RootPath);
}
}
IPostConfigureOptions is registered the same way as IConfigureOptions:
// register the configurator
services.ConfigureOptions<ModuleOptionsPostConfigurator>();
You can also combine the two interfaces in one implementing class, which I have often found a case for.
See the official documentation for more information on the options patterns.
I'm using Simple Injector as DI Container in a project.
The problem is that I have a SqliteStorage-class, which needs the path to the db. There are multiple dbs, so I need a way to inject the path to the SqliteStorage-class at creation.
My code looks as follows (simplified without interfaces):
public class SqliteStorageOptions
{
public string Path {get; set;}
}
public class SqliteStorage
{
private readonly string _path;
public SqliteStorage(SqliteStorageOptions options)
{
_path = options.Path;
}
}
public class Db1
{
private readonly SqliteStorage _sqlite;
public Db1(SqliteStorage sqlite)
{
_sqlite = sqlite;
}
}
public class Db2
{
private readonly SqliteStorage _sqlite;
public Db1(SqliteStorage sqlite)
{
_sqlite = sqlite;
}
}
// without di
var db1 = new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" });
var db2 = new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
Possible Solutions:
Include SqliteStorageOptions as parameter at every method in SqliteStorage.
Provide a init-method in SqliteStorage
Create a SqliteStorageFactory with a public SqliteStorage Create(SqliteStorageOptions options)-method.
So are there any built-in solution to my problem in simple-injector or can someone provide another (better) solution?
Thanks
Edit 1:
I added some code. Db1 and Db2 both connect to sqlite-dbs (different dbs, different schema), so I wanted to extract all the sqlite-stuff to its own class SqliteStorage. So, the SqliteStorage needs to know the db path.
Which solution is best depends a bit on whether you require Auto-Wiring (automatic constructor injection) or not. Using conditional registrations (using RegisterConditional) is a good pick, but you have be aware that it is limited to determining the injection based on only its direct parent. This means that you can't make SqliteStorageOptions conditional based on its parent parent (either Db1 or Db2).
If the Db1 and Db2 classes solely depend on a SqliteStorage and don't require any other dependencies, Auto-Wiring is not a real issue and your registrations can be as simple as the following:
container.Register<Db1>(
() => new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }));
container.Register<Db2>(
() => new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
In case Auto-Wiring is required inside Db1 and Db2, RegisterConditional gives a good alternative, because it enables Auto-Wiring:
container.Register<Db1>();
container.Register<Db2>();
container.RegisterConditional<SqliteStorage>(
Lifestyle.CreateRegistration(
() => new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }),
container),
c => c.Consumer.ImplementationType == typeof(Db1));
container.RegisterConditional<SqliteStorage>(
Lifestyle.CreateRegistration(
() => new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" }),
container),
c => c.Consumer.ImplementationType == typeof(Db2));
In this code snippet, both Db1 and Db2 are registered 'normally', while the SqliteStorage registrations are conditionally injected based on thei consumer.
This registration is more complex, because RegisterConditonal need to be supplied with a Registration instance: there is no RegisterConditional overload that directly accepts a Func<T> factory delegate.
You can have 2 singletons one per each database connection. Let's consider an example, firstly we'll need to create an interface for your StorageService:
public interface IStorage
{
void UsePath();
}
Now let's create couple of implementations of this storage service:
public class RedisStorage: IStorage
{
private readonly string _path;
public RedisStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
public class SqlStorage: IStorage
{
private readonly string _path;
public SqlStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
Enum to differentiate between implementations of IStorage:
public class StorageSource
{
public enum StorageTypes
{
Redis=1,
Sql=2
}
}
Once we are done with that, let's create a wrapper for a storage source:
public interface IStorageWrapper
{
void DoStuff();
}
Now comes a tricky part, instantiate a storage wrapper service decorator:
public class StorageServiceWrapper: IStorageWrapper
{
private readonly Func<string, IStorage> _storage;
public StorageServiceWrapper(Func<string, IStorage> storage)
{
_storage = storage;
}
public void UsePath()
{
_storage(StorageSource.StorageTypes.Redis.ToString()).DoStuff();
//uncomment for sql
//_storage(StorageSource.StorageTypes.Sql.ToString()).DoStuff();
}
}
To achieve this, you will need to register your classes in Startup.cs as follows:
services.AddScoped<IStorageWrapper, StorageServiceWrapper>();
services.AddSingleton<RedisStorage>();
services.AddSingleton<SqlStorage>();
services.AddTransient<Func<string, IStorage>>(serviceProvider => key =>
{
switch (key)
{
case "Redis":
return serviceProvider.GetService<RedisStorage>();
default:
return serviceProvider.GetService<SqlStorage>();
}
});
This wouldn't be as beautiful as calling _storage.DoStuff();, but I believe would help you with the solution of your problem. If you still want to keep it handy, consider managing your settings file and injecting proper IOptions<> instance with a conn string you need and registering a factory method.
I'm reviewing a code I wrote sometime before and I noticed I did in past
public class Linq2DbSettings : ILinqToDBSettings
{
public IEnumerable<IDataProviderSettings> DataProviders
{
get { yield break; }
}
public string DefaultConfiguration =>
"SqlServer"; // lets set your configuration as default, so you can call just new DataContext() or new DataConnection()
public string DefaultDataProvider => ProviderName.SqlServer; // and set default database type
public IEnumerable<IConnectionStringSettings> ConnectionStrings
{
get
{
yield return
new ConnectionStringSettings
{
Name = "SqlServer",
ProviderName = "SqlServer",
ConnectionString =ConfigurationManager.ConnectionStrings["default"].ConnectionString
};
}
}
}
public class ConnectionStringSettings : IConnectionStringSettings
{
public string ConnectionString { get; set; }
public string Name { get; set; }
public string ProviderName { get; set; }
public bool IsGlobal => false;
}
Even if it's related to Linq2Db it appies to all classes where I need to resolve the container.
As you can see I'm using here ConfigurationManager.ConnectionStrings["default"] while it would be best to use IConfiuration from Microsoft.Extensions.Configuration
To do so I should resolve the IConfiguration item, registered in SimpleInjector's Container.
In past I used a wrapper
public static class ContainerWrapper
{
public static Container Container { get; set; }
}
and I assigned it as
ContainerWrapper.Container = container;
container.Verify();
But I think it's a wrong approach, what's the best solution?
My advise is the following:
Keep your configuration objects narrow; don't create wide configuration objects that contain a large set of propertiesand are used by many consumers.
Prevent configuration objects from reading from the configuration system. Make them immutable behaviorless data objects instead, and supply them with configuration values in their constructor. This prevents the configuration object from becoming a Volatile Dependency.
Remove the interfaces on your configuration objects. Interfaces are meant to hide behavior, but the configuration objects should only contain data.
Load configuration values during application startup, and register those configuration objects as Singleton in the container.
To access App Keys in a class library, do we need to do the following code in every class library and class where we need to access a AppKey?
public static IConfigurationRoot Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
This is what I found in Microsoft docs, but this looks very redundant.
Startup class in a project as below
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework().AddEntityFrameworkSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:MyDb:ConnectionString"]));
}
}
Then how should I inject this "IConfigurationRoot" in each class of a project. And do I have to repeat this Startup class in each class Library? Why is this not part of .NET Core Framework?
The recommended way is to use the options pattern, provided by Microsoft and used heavily in ASP.NET Core.
Basically you create a strong typed class and configure it in the Startup.cs class.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
and initialize it in the Startup class.
// load it directly from the appsettings.json "mysettings" section
services.Configure<MySettings>(Configuration.GetSection("mysettings"));
// do it manually
services.Configure<MySettings>(new MySettings
{
Value1 = "Some Value",
Value2 = Configuration["somevalue:from:appsettings"]
});
then inject these options everywhere you need it.
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
By the principle of Information Hiding in Object-Oriented Programming, most classes should not need to have access to your application configuration. Only your main application class should need to directly have access to this information. Your class libraries should expose properties and methods to alter their behavior based on whatever criteria their callers deem necessary, and your application should use its configuration to set the right properties.
For example, a DateBox shouldn't need to know how timezone information is stored in your application configuration file - all it needs to know is that it has a DateBox.TimeZone property that it can check at runtime to see what timezone it is in.
How can keep all the configuration file code out of my logic code using Settings (ApplicationSettingsBase) and Dependency Injection?
With configuration I mean a customer specific configuration file.
Do I really have to inject a configuration class everytime I need it or is there another pattern?
It would be great to get some sample code!
Samples:
Static Configuration:
public static class StaticConfiguration
{
public static bool ShouldApplySpecialLogic { get; set; }
public static string SupportedFileMask { get; set; }
}
public class ConsumerOfStaticConfiguration
{
public void Process()
{
if (StaticConfiguration.ShouldApplySpecialLogic)
{
var strings = StaticConfiguration.SupportedFileMask.Split(',');
foreach (var #string in strings)
{
}
}
}
}
Non static Configuration:
public interface IConfiguration
{
bool ShouldApplySpecialLogic { get; set; }
string SupportedFileMask { get; set; }
}
public class Configuration : IConfiguration
{
public bool ShouldApplySpecialLogic { get; set; }
public string SupportedFileMask { get; set; }
}
public class Consumer
{
private readonly IConfiguration _configuration;
public Consumer(IConfiguration configuration)
{
_configuration = configuration;
}
public void Process()
{
if (_configuration.ShouldApplySpecialLogic)
{
var strings = _configuration.SupportedFileMask.Split(',');
foreach (var #string in strings)
{
}
}
}
}
Static Context with non static configuration:
public static class Context
{
public static IConfiguration Configuration { get; set; }
}
public class ConsumerOfStaticContext
{
public void Process()
{
if (Context.Configuration.ShouldApplySpecialLogic)
{
var strings = Context.Configuration.SupportedFileMask.Split(',');
foreach (var #string in strings)
{
}
}
}
}
Configuration classes reduce cohension and increase coupling in the consumers. This is because there may be many settings that don't relate to the one or two needed by your class, yet in order to fulfill the dependency, your implementation of IConfiguration must supply values for all of the accessors, even the irrelevant ones.
It also couples your class to infrastructure knowledge: details like "these values are configured together" bleed out of the application configuration and into your classes, increasing the surface area affected by changes to unrelated systems.
The least complex, most flexible way to share configuration values is to use constructor injection of the values themselves, externalizing infrastructure concerns. However, in a comment on another answer, you indicate that you are scared of having a lot of constructor parameters, which is a valid concern.
The key point to recognize is that there is no difference between primitive and complex dependencies. Whether you depend on an integer or an interface, they are both things you don't know and must be told. From this perspective, IConfiguration makes as much sense as IDependencies. Large constructors indicate a class has too much responsibility regardless of whether the parameters are primitive or complex.
Consider treating int, string and bool like you would any other dependency. It will make your classes cleaner, more focused, more resistant to change, and easier to unit test.
The important part to realize is that configuration is only one among several sources of values that drive your application's behavior.
The second option (non-static configuration) is best because it enables you to completely decouple the consumer from the source of the configuration values. However, the interface isn't required, as configuration settings are normally best modeled as Value Objects.
If you still want to read the values from a configuration file, you can do that from the application's Composition Root. With StructureMap, it might looks something like this:
var config = (MyConfigurationSection)ConfigurationManager.GetSection("myConfig");
container.Configure(r => r
.For<Consumer>()
.Ctor<MyConfigurationSection>()
.Is(config));
One way is to inject a configuration interface like you post. Here are a couple other ways.
Exposing a Setter
class Consumer
{
public bool ShouldApplySpecialLogic { get; set; }
...
}
In the composition root, you can read a config file or hardcode it. Autofac example:
builder.RegisterType<Consumer>().AsSelf()
.OnActivated(e => e.Instance.ShouldApplySpecialLogic = true);
This is probably only advisable when you have a good default
Constructor Injection
public class Server
{
public Server(int portToListenOn) { ... }
}
In the composition root:
builder.Register(c => new Server(12345)).AsSelf();
In my applications I do what you have done above with IoC. That is to say, having my IoC container (StructureMap also) inject an IApplicationSettings into my classes.
For example, in an ASP.NET MVC3 project it may look like:
Public Class MyController
Inherits Controller
...
Private ReadOnly mApplicationSettings As IApplicationSettings
Public Sub New(..., applicationSettings As IApplicationSettings)
...
Me.mApplicationSettings = applicationSettings
End Sub
Public Function SomeAction(custId As Guid) As ActionResult
...
' Look up setting for custId
' If not found fall back on default like
viewModel.SomeProperty = Me.mApplicationSettings.SomeDefaultValue
Return View("...", viewModel)
End Function
End Class
My implementation of IApplicationSettings pulls most things from the app's .config file and has a few hard-coded values in there as well.
My example wasn't logic flow-control (like your example), but it would have worked just the same if it was.
The other way to do this would be to do a service-locator type pattern, where you ask your Dependency Injection container to get you an instance of the configuration class on-the-fly. Service-Location is considered an anti-pattern generally, but might still be of use to you.