28

I have an ASP.NET 5 MVC Web Application and in Startup.cs I see that the public property

IConfigurationRoot Configuration 

is being set to builder.Build();

Throughout the MVC Web Application I can simply do

Startup.Configuration["Data:DefaultConnection:ConnectionString"]

to get the conn string from the appsettings.json file.

How can I get the connection string specified in the ASP.NET 5 MVC appsettings.json passed down to my Repository Class Library using constructor injection?

UPDATE:
Here is the base repository that all other repositories inherit from (as you can see I have a hardcoded connection string in here for now):

public class BaseRepo
{
    public static string ConnectionString = "Server=MYSERVER;Database=MYDATABASE;Trusted_Connection=True;";

    public static SqlConnection GetOpenConnection()
    {
        var cs = ConnectionString;
        var connection = new SqlConnection(cs);
        connection.Open();
        return connection;
    }
}

In my asp.net 5 web application in my appsettings.json file I have the following which is equivalent to adding a connection string to a web.config in a .net 4.5 webapp:

  "Data": {
    "DefaultConnection": {
      "ConnectionString": "Server=MYSERVER;Database=MYDATABASE;Trusted_Connection=True;"
    }
  }

Additionally in my asp.net 5 web application I have the following default code in my Startup.cs which loads the sites configuration into a public property of type IConfigurationRoot:

 public IConfigurationRoot Configuration { get; set; }
// Class Constructor
        public Startup(IHostingEnvironment env)
        {
            // Set up configuration sources.
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

            if (env.IsDevelopment())
            {
                // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
                builder.AddUserSecrets();
            }

            builder.AddEnvironmentVariables();
            Configuration = builder.Build();
        }

Now in my asp.net web application if I would like to access any of the appsettings I can simple do the following: Startup.Configuration["Data:DefaultConnection:ConnectionString"]

But unfortunately I can't do this from my class library..

If someone wants to try and figure this out here are the steps to reproduce:

  1. Create a new ASP.NET 5 MVC Web App.
  2. Add another project of type Class Library (Package) to the project.
  3. Figure out a way to pass appsettings from the ASP.NET 5 MVC App to the Class Library

After updating I still can't quite get it. Here is my code:

public class BaseRepo
{
    private readonly IConfigurationRoot config;

    public BaseRepo(IConfigurationRoot config)
    {
        this.config = config;
    }
}

This class declaration does not work since BaseRepo requires a constructor param now.

public class CustomerRepo : BaseRepository, ICustomerRepo
{
    public Customer Find(int id)
    {
        using (var connection = GetOpenConnection())
        {
            ...
        }
    }
}
Blake Rivell
  • 13,105
  • 31
  • 115
  • 231
  • 1
    You shouldn't have to pass your connection string around, it should be specified in your web.config and you should retrieve it in one location within your application code. Where ever that single point is, it should be put as a dependency within the classes that require it. – Luke Jan 26 '16 at 13:40
  • 1
    In ASP.NET 5 there is no Web.Config and things are done slightly differently. I know how to set my connection string in my web application but I believe in ASP.NET 5 I can pass it to my Data Repository Class Library using constructor injection. – Blake Rivell Jan 26 '16 at 13:42
  • You just taught me something new. I didn't realise that the app.config had gone now I'm the latest version. Time for me to go and learn more... :o) – Luke Jan 26 '16 at 13:53
  • 1
    This article goes a bit more in depth and I am trying to use it to come up with a solution. It seems like the solution is to create a custom AppSettings class. https://weblog.west-wind.com/posts/2015/Jun/03/Strongly-typed-AppSettings-Configuration-in-ASPNET-5 – Blake Rivell Jan 26 '16 at 13:57
  • So the problem is that you can't read a value from your configuration file? or that you can't pass this value to your library? – Manos Pasgiannis Jan 28 '16 at 09:30
  • 4
    @ManosPasgiannis I would like to know how to get any values from my appsettings.json file down to my repository class library. I am aware that when using EntityFramework the DbContext is already inejcted down to the repository class library. I am not using Entity Framework I am just using Dapper with a simply string based sql repository. So I end up creating a SQL Connection using the connection string (which I don't have) and then querying my database. I just need a way to get any Application level settings down to my repository class library or business class library with ASP.NET 5.. – Blake Rivell Jan 28 '16 at 13:40
  • Then you have to pass the connection string as a parameter where you use your class library. Provide some code from a class that uses your library and also your library's configuration class. – Manos Pasgiannis Jan 28 '16 at 14:02
  • @ManosPasgiannis I updated my post - everything under UPDATE:. If you have experience working with ASP.NET 5 then it should make sense otherwise I am not sure how much it will. – Blake Rivell Jan 28 '16 at 14:18
  • 1
    @BlakeRivell +1. This is the EXACT setup I am moving to....ASP.NET 5 with Dapper. I've been lost on how to do this for 2 weeks!!! – ganders Jan 29 '16 at 13:30
  • 1
    @ganders Glad I am not the only person. It is hard to find any ASP.NET 5 content that doesn't involve EF, – Blake Rivell Jan 29 '16 at 13:32
  • No kidding, it's like MS has an agenda or something... – ganders Jan 29 '16 at 13:33
  • 2
    Can you update with your working version? – ganders Feb 24 '16 at 18:37
  • So how are you calling your `CustomerRepo`? – sakura-bloom May 19 '17 at 20:24

7 Answers7

32

on your Startup.cs file add the following method

public void ConfigureServices(IServiceCollection services) {
    services.AddSingleton(_ => Configuration);
}

then update your BaseRepo class like this

public class BaseRepo {
    private readonly IConfiguration config;

    public BaseRepo(IConfiguration config) {
        this.config = config;
    }

    public SqlConnection GetOpenConnection() {
        string cs = config["Data:DefaultConnection:ConnectionString"];
        SqlConnection connection = new SqlConnection(cs);
        connection.Open();
        return connection;
    }
}
Manos Pasgiannis
  • 1,693
  • 1
  • 18
  • 30
  • 1
    This is exactly what I was looking for! Thanks so much. – Blake Rivell Jan 28 '16 at 14:45
  • 1
    There is one problem here. When my children repositories inherit from BaseRepo I get an error saying: BaseRepo does not contain a parameterless constructor. I am trying to do something like public class CustomerRepo: BaseRepo, ICustomerRepo – Blake Rivell Feb 10 '16 at 19:19
  • Why don't you create one? – Manos Pasgiannis Feb 10 '16 at 19:32
  • Ugh! yeah I wasn't thinking so I added a paramaterless constructor to the BaseRepo class in addition with the constructor you told me to write. So everything appears to be setup correctly but when I run my application and make a database call config.GetSection("Data:DefaultConnection:ConnectonString").ToString(); doesn't work because config is null.. It is never automatically injected into my BaseRepo class.. Any ideas? – Blake Rivell Feb 10 '16 at 19:43
  • Well, if I understand correctly, you have to remove the parameterless constructor from your base class and also remove any parameterless constructors from your children repositories. Children repositories constructors should inject your config like the baserepo does – Manos Pasgiannis Feb 10 '16 at 20:11
  • I am unaware of the syntax.. So lets say CustomerRepo inherits from BaseRepo like this: public class CustomerRepo : BaseRepo, ICustomerRepo, this currently errors since BaseRepo now requires a constructor param. How do you specify inheritance and a constructor param at the same time? – Blake Rivell Feb 10 '16 at 20:17
  • Please see the bottom of my post (I updated it) to see what I am talking about. – Blake Rivell Feb 10 '16 at 20:21
  • 1
    I figured it out! The key was figuring out how to pass a param to a base class like so: public CustomerRepo(IConfigurationRoot config) : base(config) { } – Blake Rivell Feb 10 '16 at 22:23
  • I think you made a mistake in the code. You can't have `GetOpenConnection()` static, and also access the instance `config` field member. – James Wilkins Mar 11 '17 at 23:15
  • @JamesWilkins You are right. GetOpenConnection doesn't need to be static. I will edit the answer to fix this – Manos Pasgiannis Mar 12 '17 at 11:02
  • 1
    instead of >>>> string cs = config["Data:DefaultConnection:ConnectionString"] >>>>> use this >>>> string cs = config.GetConnectionString("DefaultConnection"); – Nick G. Nov 01 '17 at 16:50
  • When is the connection being closed ? – Frederik Gheysels Apr 26 '19 at 14:24
  • How does Base Repo even work c# does not support multiple base classes – c-sharp-and-swiftui-devni Oct 09 '19 at 12:41
  • This approach isn't scalable. You can't switch to a different test environment without changing the code in the infrastructure project. Application and infrastructure layer shouldn't know to which specific server and db it is talking to, so you can inject a different environment's connection strings. – HamsterWithPitchfork Aug 01 '20 at 07:15
  • @metabuddy the connection string is injected already in this answer and can be easily configured to use a different connection string when deployed to a different environment. – Manos Pasgiannis Aug 01 '20 at 16:12
  • @BlakeRivell, Thanks. This did the trick `public ItemRepository(IConfiguration configuration) : base(configuration)` – Vishal Gamji Mar 16 '21 at 21:39
15

ASP.NET provides its own way of passing around configuration settings.

Suppose you have the this in your appSettings.json:

{
  "Config": {
    "Setting1": 1,
    "Setting2": "SO"
  }
}

Then you need a class like this:

public class MyConfiguration
{
    public int Setting1 { get; set; }

    public string Setting2 { get; set; }
}

This allows you to configure your service with this configuration by adding the following line

services.Configure<MyConfigurationDto>(Configuration.GetSection("Config"));

to ConfigureServices.

You can then inject the configuration in constructors by doing the following:

public class SomeController : Controller
{
    private readonly IOptions<MyConfiguration> config;

    public ServiceLocatorController(IOptions<MyConfiguration> config)
    {
        this.config = config;
    }

    [HttpGet]
    public IActionResult Get()
    {
        return new HttpOkObjectResult(config.Value);
    }
}

This example is for controllers. But you can do the same with other layers of you application.

Chrono
  • 1,433
  • 1
  • 16
  • 33
3

I have a constructor in my repository class that accepts the db connection string as a parameter. This works for me when I add my repository for injection. In ConfigureServies() of the startup.cs file add this:

services.AddScoped<IRepos>(c => new Repos(Configuration["DbConnections:ConnStr1"]));

IRepos.cs is the interface, Repos.cs is the class that implements it. And of course Configuration is just a reference to the built IConfigurationRoot object.

uTeisT
  • 2,256
  • 14
  • 26
Matthew Allen
  • 538
  • 2
  • 7
  • 14
3

A slightly different approach would be to make a static class in your Class Library on which you call a method from the Configure(..)-method in Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    ConnectionManager.SetConfig(Configuration);
}

In this case, I've added Configuration as a Singleton in ConfigureServices:

services.AddSingleton(_ => Configuration);

My ConnectionManager looks like this:

public class ConnectionManager
{
    private static IConfiguration currentConfig;

    public static void SetConfig(IConfiguration configuration)
    {
        currentConfig = configuration;
    }

    /// <summary>
    /// Get a connection to the database.
    /// </summary>
    public static SqlConnection GetConnection
    {
        get
        {
            string connectionString = currentConfig.GetConnectionString("MyConnection");
            // Create a new connection for each query.
            SqlConnection connection = new SqlConnection(connectionString);
            return connection;
        }
    }
}

This may or may not have some issues regarding object lifetimes and such, and I'm certainly no fan of static classes but as far as I can tell it's a viable approach. Instead of passing Configuration you could even extract the ConnectionString from the config-file and send only that.

Ruben Steins
  • 2,782
  • 4
  • 27
  • 48
  • Love this approach. bcz i need the connection string in my 4th layer and i am not happy to pass Configuration all over the layers. – Muzafar Khan Oct 16 '20 at 12:17
2

There is already an extension method you can use to get connection strings specifically from aspsettings.json.

  1. Define your connection strings in appsettings.json like this:

     {
         "ConnectionStrings": {
             "Local": "Data source=.\\SQLExpress;Initial Catalog=.......",
             "Test:": "Data source=your-server;......"
         },
         "Logging": {
             "IncludeScopes": false,
             "LogLevel": {
                 "Default": "Debug",
                 "System": "Information",
                 "Microsoft": "Information"
             }
         }
    }
    
  2. In your public void ConfigureServices(IServiceCollection services) method inside your Startup.cs, you can get the connection string like this:

    var connectionString = this.Configuration.GetConnectionString("Local");
    
  3. The GetConnectionString extension is from Microsoft.Extensions.Configuration.
  4. Enjoy :)

UPDATE

I didn't want to go into details at first because the question here had already been marked as answered. But I guess I can show my 2 cents here.

If you do need the whole IConfigurationRoot object injected into Controllers, @Chrono showed the right way.

If you don't need the whole object, you should just get the connection string, pass it into the DbContext inside the ConfigureServices() call, and inject the DbContext into Controllers. @Prashant Lakhlani showed it correctly.

I am just saying, in @Prashant Lakhlani post, you can use GetConnectionString extension instead to clean up the code a little bit.

David Liang
  • 20,385
  • 6
  • 44
  • 70
  • The limitation of this is you need a reference to the Configuration object in the class. It is present in Startup.cs but you need to inject it into the class. – ChrisP Feb 27 '17 at 22:11
1

If ConfigureServices in your project's startUp.cs contains

services.AddEntityFramework()
             .AddSqlServer()
             .AddDbContext<YourDbContext>(options =>
                 options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));

and your repository cs file is having constructor injection as shown below

public class MyRepo : IRepo
{
    private readonly YourDbContext dbContext;
    public MyRepo(YourDbContext ctx)
    {
        dbContext = ctx;
    }
}

YourDbContext will be automatically resolved.

Prashant Lakhlani
  • 5,758
  • 5
  • 25
  • 39
  • 2
    Unfortunately I am not using Entity Framework so I don't have a DbContext setup. I am using Dapper in my DAL so I only need the application's connection string to create a SQL connection. Is there still a way to get it from the application's settings down to the repository? – Blake Rivell Jan 27 '16 at 13:38
  • it looks from the code that you are doing it right, can you please provide generalize code of how your repository looks like? – Prashant Lakhlani Jan 27 '16 at 13:59
0

What you need is to create a class in class library project to access the appsettings.json in website project and return connection string.

{
    private static string _connectionString;

    public static string GetConnectionString()
    {
        if (string.IsNullOrEmpty(_connectionString))
        {
            var builder = new ConfigurationBuilder()
           .AddJsonFile("appsettings.json");
            Configuration = builder.Build();
            _connectionString = Configuration.Get<string>("Data:MyDb:ConnectionString");
        }
        return _connectionString;
    }
    public static IConfigurationRoot Configuration { get; set; }

}
samvietnam
  • 16
  • 2