Get Connection String through Dependency Injection - c#

I am developing a blazor application. I want to have connection string and some other key in a class as service.
For that I have created an interface
interface IDbConnector
{
string ConnectionString { get; set; }
bool SomeKey { get; set; }
}
and in my class I want to have something like that
using Microsoft.Extensions.Configuration;
public class DbConnector : IDbConnector
{
private IConfiguration Configuration { get; set; }
public DbConnector(IConfiguration configuration)
{
Configuration = configuration;
}
public string ConnectionString = Configuration.GetConnectionString();
public bool SomeKey = Configuration.GetSection("xyz");
}
I can register it as a service with
services.AddScoped<IDbConnector, DbConnector>();
But inside DbConnector class it says
A fiels initializer can not refrence the non static field ,method or
property DbConnector.Configuration
Pardon for my coding pattern as I am new to DI concept. Please suggest if there is another and better way to do this.

You've made a syntax error, these should be expression bodied property accessors. = to =>
public string ConnectionString => Configuration.GetConnectionString();
public bool SomeKey => Configuration.GetSection("xyz");
Instead you've tried to initialise them as fields. Fields initialise pre-constructor, therefore cannot access the configuration.

services.AddConfiguration() // enable Configuration Services
var config = new WeblogConfiguration();
Configuration.Bind("Weblog", config); // <--- This
services.AddSingleton(config);

Related

Injecting configuration objects in a .NET application

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.

Proper way to inject custom configuration

The problem to solve:
There is a list of settings lets say:
{
"Kind1":
{"attr1":"val11"},
{"attr2":"val12"},
"Kind2":
{"attr1":"val21"},
{"attr2":"val22"},
}
and a consumer class (controller) in .NET Core 2.1, needs to access the above configuration to use Kind1 or Kind2.
Supposing corresponding class is already defined in C#:
public class KindSetting
{
public string attr1{get;set;}
public string attr2{get;set;}
}
Now what is the best way of inject the configuration into the consumer object.
Is there a way to inject an IConfiguration instance into the consumer object and use it like this?:
KindSetting kindSetting =_configuration.GetValue<KindSetting>(kindSettingKey);
Is there any better approach to fulfill the above requirement?
In the startup.cs file, in the ConfigureServices method you can do a configuration. Sample code below:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Need to add following lines
services.Configure<KindSetting>(Configuration.GetSection("Kind1"));
}
After adding into services, you can inject this configuration in your class, like follow:
public class HomeController : Controller
{
private readonly IOptions<KindSetting> _KindSetting;
public HomeController(IOptions<KindSetting> KindSetting)
{
_KindSetting = KindSetting
}
public void myFunction()
{
var mysetting = _KindSetting.Value.attr1
}
}
I used the following approach, however I am not sure be the best
in startup class configure method:
services.Configure<List<KindSetting>>(Configuration.GetSection("KindSettingList"));
and in consumer object side:
public ConsumerController(IOptions<List<KindSetting>> kindSettingsListAccessor,...)
After adding services.Configure<KindSettings>, you can inject configuration via DI by adding to your constructor.
IOptionsSnapshot<KindSettings> kindSettingsConfiguration
or
IOptions<KindSettings> kindSettingsConfiguration
The difference is, IOptionsSnapshot will reflect live changes in your config file, while IOptions is for singleton use.
Edit after comment:
Lets say your configuration file looks like this:
{
"Kind1":
{"attr1":"val11"},
{"attr2":"val12"},
"Kind2":
{"attr1":"val21"},
{"attr2":"val22"},
}
To successfully bind this, you would need two configuration classes
public class Kind1Configuration
{
public string Attr1 { get; set; }
public string Attr2 { get; set; }
}
public class Kind2Configuration
{
public string Attr1 { get; set; }
public string Attr2 { get; set; }
}
And as previously stated, to connect the dots just add
public void ConfigureServices(IServiceCollection services)
{
services.Configure<Kind1Configuration>(Configuration.GetSection("Kind1"));
services.Configure<Kind2Configuration>(Configuration.GetSection("Kind2"));
}
To use this in a controller, add your IOptions to the constructor
public class TestController(IOptionsSnapshot<Kind1Configuration> kindSettingsConfiguration)
{
Kind1Configuration configuration = kindSettingsConfiguration.Value;
}
Hope it helps.

Constructor with non-DI and DI parameters

I'm creating a service that requires some config parameters and a logger. Here is the constructor for my service:
public StorageProvider(string directory, ILogger<StorageProvider> logger)
I just added the logger. I used to initalize it like this in my startup.cs:
services.AddSingleton<IStorageProvider>(
new StorageProvider(Configuration["TempStorage.Path"]));
The directory parameter comes from the config file, and the logger gets DI'ed. How do I setup my IStorageProvider?
You should do the following:
Wrap the configuration value TempStorage:Path into its own configuration class, e.g. StorageProviderSettings.
Let StorageProvider depend upon that new configuration class.
Register that configuration class as singleton into the ASP.NET configuration system.
Example:
public sealed class StorageProviderSettings
{
public readonly string TempStoragePath;
public StorageProviderSettings(string tempStoragePath)
{
if (string.IsNullOrWhiteSpace(tempStoragePath))
throw new ArgumentException(nameof(tempStoragePath));
this.TempStoragePath = tempStoragePath;
}
}
public sealed class StorageProvider : IStorageProvider
{
public StorageProvider(
StorageProviderSettings settings, ILogger<StorageProvider> logger)
{
// ...
}
}
// Registration
services.AddSingleton(new StorageProviderSettings(Configuration["TempStorage.Path"]));
services.AddSingleton<IStorageProvider, StorageProvider>();
Use the Options pattern as Tratcher suggests in a comment. Read more in the official docs on Configuration.
Basically you define a class to be hold the value you need:
public class StorageProviderOptions
{
public string TempStoragePath { get; set; }
}
Then in ConfigureServices you register the type:
services.Configure<StorageProviderOptions>();
In your code, you request IOptions<StorageProviderOptions> and set this to an instance of StorageProviderOptions:
public class SomeController
{
private readonly StorageProviderOptions _options;
public SomeController(IOptions<StorageProviderOptions> options)
{
_options = options.Value;
}
}
Finally, make sure you have an element in your configuration source that matches the TempStoragePath name. Alternately, you can register the option in ConfigureServices using code:
services.Configure<ServiceProviderOptions>(o => o.TempStoragePath = "temp");

Access App key data from class libraries in .NET Core / ASP.NET Core

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.

Accessing strongly typed configuration settings directly into class library in ASP.NET 5 (vNext)?

I have an ASP.NET 5 MVC 6 application. It has a Data Access library which needs a connection string to make a connection to the database.
Currently I am passing a strongly typed configuration settings class with connection string as a public property all the way up from the MVC controllers (Where it is received through DI) to the Data Access Class library.
I want to know if there is a better way for a class library to access strongly typed configuration settings using dependency injection or any other mechanism ?
Thank you.
EDIT : Code Example
This is a generic DbTransaction class which is called from the business layer.
public class DbTransactions<TEntity> where TEntity : DbEntity, new()
{
private readonly Query _query;
public DbTransactions(string connectionString)
{
_query = new Query(connectionString);
}
public TEntity GetById(long id)
{
var sqlGenerator = new SqlGenerator<TEntity>();
var sql = sqlGenerator.GetSelectByIdQuery();
var mapper = new NMapper.Mapper<TEntity>();
var cmd = _query.GetNpgsqlCommand(sql, new { id });
return mapper.GetObject(cmd);
}
}
The query class creates the connection object from the connection string that is provided to it.
I agree with #Steven that using IOptions<T> is a bad idea. You can however use the ConfigurationBinder extensions to read out a specific section of configuration into a strongly-typed POCO class. Just make sure you have this somewhere in your project.json's dependencies section:
"dependencies": {
[other dependencies],
"Microsoft.Extensions.Configuration.Binder": "1.0.0-rc1-final",
[other dependencies]
}
Just build up your configuration as normal. For example, say you had a Database.json configuration file that looked like this:
{
"Database": {
"ConnectionInfo": {
"connectionString": "myConnectionString"
}
}
}
You can build your configuration from the Startup method in Startup.cs:
public IConfiguration Configuration { get; private set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) {
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("Database.json")
.AddEnvironmentVariables()
Configuration = configBuilder.Build();
}
Now we can make a POCO class to match the "Database:ConnectionInfo" section of the JSON configuraiton file. You can match it to an interface as #janhartmann suggests, but it may or may not be necessary.
public class DatabaseConnectionInfo {
public string ConnectionString { get; set; }
}
Now, how can we get that DatabaseConnectionInfo class populated with the data from the JSON config file? One way is to use the IOptions<T> framework type, but I don't like using framework types when I can avoid them. Instead, you can get an instance like so:
DatabaseConnectionInfo dbConnInfo = Configuration
.GetSection("Database:ConnectionInfo")
.Get<DatabaseConnectionInfo>();
Now you can just register the dbConnInfo type as a singleton of the type DatabaseConnectionInfo (or as a singleton of an interface type if you prefer to have an immutable configuration settings object). Once it's registered in the IoC container, you can constructor inject it where needed:
public class DbTransactions<TEntity> where TEntity : DbEntity, new()
{
private readonly Query _query;
public DbTransactions(DatabaseConnectionInfo dbConnInfo)
{
_query = new Query(dbConnInfo.ConnectionString);
}
public TEntity GetById(long id) { ... }
}
You can let your service class depend on a an interface, e.g.:
public interface IConnectionFactory {
string ConnectionString();
}
public class MyDataAccessClass {
private readonly IConnectionFactory _connectionFactory
public MyDataAccessClass(IConnectionFactory connectionFactory) {
_connectionFactory = connectionFactory;
}
public void Whatever() {
var connectionString = _connectionFactory.ConnectionString();
}
}
And then make an implementation of it (as near to your composition root as possible):
public class SqlConnectionFactory : IConnectionFactory {
public string ConnectionString() {
return "myConnectionString";
}
}
Let the interface have the methods or properties you need.
Wire like:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConnectionFactory, SqlConnectionFactory>();
}
I use a similar method to some of those listed earlier, but I think its sufficiently different to warrant another answer.
Firstly I define an interface with all the configuration that my class needs. In this case
public interface IDbTransactionsConfiguration {
string ConnectionString { get; }
}
Then I alter my class to take this configuration via constructor injection
public class DbTransactions<TEntity> where TEntity : DbEntity, new() {
public DbTransactions(IDbTransactionsConfiguration configuration) {
...
}
}
Then I define a class that handles all the configuration for my application.
public class MyApplicationConfiguration : IDbTransactionsConfiguration, ISomeOtherConfiguration, etc {
public string ConnectionString { get; }
... other configuration
}
Then I pass this class into all classes that need it using some kind of Depenendency Injection (normally Castle Windsor or AutoFac for me).
If it is too difficult to construct DbTransactions for legacy type reasons, I define a static version of MyApplicationConfiguration and access this directly.
More details on this blog post.

Categories