6

In my solution, I have a ASP.NET Core web project and a .NET Standard class library project. Class library project is the data access layer and I want to read the connection string from my appsettings.json (ASP.NET Core project) in my data access layer.

I have found few answers such as by Andrii Litvinov which looks like quite straight forward to implement but he also mentioned about implementing through Dependency Injection. I don't want to choose the easy shortcut way but looking for the dependency injection implementation?

I am not sure if having appsettings.json in my class library and then registering it through IConfigurationRoot is the better option (as explained here by JRB) but in my scenario, the connection string is in the appsettings.json file of the web project and I wan't constructor dependency injection implementation in my class library project to consume the connection string.

Learning Curve
  • 1,449
  • 7
  • 30
  • 60
  • 1
    Would anyone mind telling me the reason for the down vote? – Learning Curve Jul 12 '18 at 11:55
  • Possible duplicate of [How to read connection string in .NET Core?](https://stackoverflow.com/questions/39083372/how-to-read-connection-string-in-net-core) – Christian Gollhardt Jul 12 '18 at 11:57
  • 1
    @Christian i can see in that question where it is mentioned about reading it in a class library project? – Learning Curve Jul 12 '18 at 11:59
  • You need to read the configuration. Better duplicate: https://stackoverflow.com/a/45845041/2441442 – Christian Gollhardt Jul 12 '18 at 12:01
  • 1
    As per my understanding, it refers to the scenario of creating a appsettings.json file in class library project and than registering it to the IConfigurationRoot. In my case my I don't have any appsettings file in my class library project and I also don't need to register the one that IConfiguration already able to access in my web project. Please correct me if I am wrong? – Learning Curve Jul 12 '18 at 12:06
  • Are you concerned about coupling the class library to IConfiguration? This appears to be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). A [mcve] should help clarify the actual problem – Nkosi Jul 12 '18 at 12:08
  • 1
    @Nkosi on coupling issue: I don't know which approach is more authentic, having an appsetting in class library and then registering it with the IConfigurationRoot or just reading it from the web project through IConfiguration constructor dependency injection, as answered by Alex. XY Problem issue: yes I can say it is gone into XY problem side with the answer of Christian while I believe my question was clear enough where I asked how I can read asp.net Core appsettings file for the connection string in my class library project. – Learning Curve Jul 12 '18 at 12:15

4 Answers4

9

You can inject an instance of a class that implements IConfiguration See Here
Let's assume in your .net core app, you have a configuration file that looks something like this:

{
  "App": {
    "Connection": {
      "Value": "connectionstring"
    }
  }
}

In your data access layer (class library) you can take a dependency on IConfiguration

public class DataAccess : IDataAccess
{
    private IConfiguration _config;

    public DataAccess(IConfiguration config)
    {
        _config = config;
    }

    public void Method()
    {
        var connectionString = _config.GetValue<string>("App:Connection:Value"); //notice the structure of this string
        //do whatever with connection string
    }
}

Now, in your ASP.net Core web project, you need to 'wire up' your dependency. In Startup.cs, I'm using this (from the default boilerplate template)

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.AddSingleton<IConfiguration>(Configuration); //add Configuration to our services collection
        services.AddTransient<IDataAccess, DataAccess>(); // register our IDataAccess class (from class library)
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}

Now, when your code in your class library gets executed, the ctor gets handed the instance of IConfiguration you have set up in your web app

Note: You can create strongly typed settings class if you'd prefer, see here for more information

Alex
  • 37,502
  • 51
  • 204
  • 332
  • In order to use IConfiguration, do I need Microsoft.Extensions.Configuration package in my data access layer project? – Learning Curve Jul 12 '18 at 11:53
  • @LearningCurve yes, you will – Alex Jul 12 '18 at 14:32
  • 3
    An update if someone is looking for the same solution. _config.GetValue is not available anymore rather it is _config.GetSection. – Learning Curve Jul 13 '18 at 12:09
  • @Alex, I think I am using your suggestion wrong, as somehow. After wiring up in Startup.cs, I was expecting the constructor to be "handed the instance of IConfiguration" but this does not seem to be happening, as I get the following error when the constructor thinks it is not getting handed what it needs: "There is no argument given that corresponds to the required formal parameter 'config' of 'ConnectionManager.ConnectionManager(IConfiguration)' " Do I have to manually pass in "config" somehow? – David Mays Oct 18 '21 at 16:59
4

I would suggest Options pattern. You can create the class with configuration data, e.g.:

public class ConnectionStringConfig
{
    public string ConnectionString { get; set; }
}

Register it on Startup:

public void ConfigureServices(IServiceCollection services)
{
   ...    
   services.Configure<ConnectionStringConfig>(Configuration);
}

and inject in your data access layer

private readonly ConnectionStringConfig config;

public Repository(IOptions<ConnectionStringConfig> config) 
{
    this.config = config.Value;
}
Alex Riabov
  • 8,655
  • 5
  • 47
  • 48
  • I already have mentioned about this approach (referred Andrii Litvinov) in my question. To my curiosity, why do you think this approach is better while Andrii by himself suggested the dependency injection? – Learning Curve Jul 12 '18 at 11:47
  • @LearningCurve it's a dependency injection approach, while Andrii's solution is rather sharing value via static class. DI approach is better in terms of testability of the application – Alex Riabov Jul 12 '18 at 11:50
  • I meant to say dependency injection through constructor. – Learning Curve Jul 12 '18 at 11:54
  • 1
    I would advise against using `IOptions` in anything but the application's Composition Root and instead let the `Repository` class depend directly on `ConnectionStringConfig`, as described [here](https://simpleinjector.org/aspnetcore#working-with-ioptions-t). – Steven Jul 13 '18 at 07:20
  • @Steven is right. Using IOptions anywhere outside the composition root is bad practice. – user10728126 Aug 30 '19 at 13:21
  • 1
    @AlexRiabov I am looking at your example and its seems the good route but how to a define which connection string name to use that bit is not clear. – c-sharp-and-swiftui-devni Nov 03 '19 at 20:50
0

It's pretty simple...use IOptions at the composition root like so in startup.cs or in a separate class library project:

  services.AddScoped<IDbConnection, OracleConnection>();
        services.AddScoped<IDbConnection, SqlConnection>();
        services.Configure<DatabaseConnections>(configuration.GetSection("DatabaseConnections"));
        services.AddScoped(resolver =>
        {
            var databaseConnections = resolver.GetService<IOptions<DatabaseConnections>>().Value;
            var iDbConnections = resolver.GetServices<IDbConnection>();
            databaseConnections.OracleConnections.ToList().ForEach(ora =>
            {
                ora.dbConnection = iDbConnections.Where(w => w.GetType() == typeof(OracleConnection)).FirstOrDefault();
                ora.dbConnection.ConnectionString = ora.ConnectionString;
                //ora.Guid = Guid.NewGuid();
            });
            databaseConnections.MSSqlConnections.ToList().ForEach(sql =>
            {
                sql.dbConnection = iDbConnections.Where(w => w.GetType() == typeof(SqlConnection)).FirstOrDefault();
                sql.dbConnection.ConnectionString = sql.ConnectionString;
                //sql.Guid = Guid.NewGuid();
            });
            return databaseConnections;
        });

Above uses the Configuration class to map the appsettings.json section that houses your connection strings. Here's an example of the appsettings.json file:

      "DatabaseConnections": {
"OracleConnections": [
  {
    "Alias": "TestConnection1",        
    "ConnectionString": "Data Source=(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ) (PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ) ) );User Id=;Password=;"
  },
  {
    "Alias": "TestConnection2",        
    "ConnectionString": "Data Source=(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ) (PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ) ) );User Id=;Password=;"
  }
],
"MSSqlConnections": [
  {
    "Alias": "Music",
    "ConnectionString": "Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename=C:\\Users\\MusicLibrary.mdf;Integrated Security=True;Connect Timeout=30"
  }
]
}

IOptions now gives me the ability to set my connection string at runtime in startup.cs close to the composition root.

Here's my class I'm using to map my connection strings:

        public class DatabaseConnections : IDatabaseConnections
        {
            public IEnumerable<Connection> OracleConnections { get; set; }
            public IEnumerable<Connection> MSSqlConnections { get; set; }
        }

Now any service layer has access to multiple db connections and provider per request!

Github project: https://github.com/B-Richie/Dapper_DAL

user10728126
  • 151
  • 4
0

This was solved for me in my program.cs file just adding this:

    builder.Services.AddDbContext<YourContextClassHere>(options =>
    {
        options.UseSqlServer(builder.Configuration.GetConnectionString("Web_Project_app_settings_connection_string_here"));
    });
jason
  • 2,219
  • 5
  • 33
  • 66
  • Note that in my project, my context class was in a class library that I referenced from the web project. This is for .Net core 6. None of the existing answers put the solution this plainly so that's why I felt it worth adding here. – jason Oct 11 '22 at 15:41