2
votes

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.

3
Just inject the IOptions<T> in the data access class.Henk Mollema
@HenkMollema: Injecting IOptions<T> into your components is a really bad idea.Steven
Do you create your data access classes in the controller actions? How does the constructors of such classes look like?Yacoub Massad
@YacoubMassad No the DataAccess classes are not accessible from the controller actions. There is a business layer that communicates between the Controllers and the DataAccess classes. Data Access classes have no properties just static methods to do CRUD operations. They have nothing on the constructor. Just the default constructor.Nabin Karki Thapa

3 Answers

4
votes

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) { ... }
}
0
votes

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>();
}
0
votes

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.