11

I'm looking for the best practice way to store a connection string in appsettings.json in a .net Core 2 MVC app (like you do in web.config in MVC 5).

I want to use Dapper not EF (I found many EF examples).

Something like this:

{
  "ConnectionStrings": {
    "myDatabase": "Server=.;Database=myDatabase;Trusted_Connection=true;"
  },

  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

Surely there are many examples online? Nothing I can find that is for .net core 2.0.

Several things have changed between 1 and 2 and I want to ensure I'm using version 2 best practices.

I've found this - but it seems to be .net core 1: Visual Studio 2017 - MVC Core - Part 05 - Connection String from appsettings.json

This uses key value pair appsettings - not the connectionstrings: Read AppSettings in ASP.NET Core 2.0

Again it's unclear if this is .net Core 1 or 2: Net Core Connection String Dapper visual studio 2017

niico
  • 11,206
  • 23
  • 78
  • 161
  • 1
    Just use `"ConnectionStrings": {...}` and [`ConfigurationExtensions.GetConnectionString`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.configurationextensions.getconnectionstring?view=aspnetcore-2.0) - this hasn't changed and follows an obvious convention (opinion-based, of course). – Kirk Larkin May 24 '18 at 11:03
  • Thanks - I don't really follow, any chance of making this into a more detailed answer? – niico May 24 '18 at 11:29
  • Nothing has changed about this since Core was first introduced. Though, in actual practice, you'd most likely use environment variables or something like Azure Key Vault, rather than appsettings.json, so you don't commit actual credentials to your source code. It's fine for local development connection strings though. – Chris Pratt May 24 '18 at 13:20
  • Interesting - even if the connection string doesn't contain login credentials (which it doesn't in my case) - just database name? – niico May 24 '18 at 13:21
  • I found this to be the more straight forward- https://stackoverflow.com/a/51780754/1042288 – Prashanth Palaniswamy Mar 11 '19 at 19:21

2 Answers2

16

Define your connection string(s) in appsettings.json

{
    "connectionStrings": {
        "appDbConnection": "..."
    }
}

Read its value on Startup

If you follow the convention and define your connection string(s) under connectionStrings, you can use the extension method GetConnectionString() to read its value.

public class Startup
{
    public IConfiguration Configuration { get; private set; }

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

    public void ConfigureServices(IServiceCollection services)
    {
        // Since you said you're using Dapper, I guess you might want to
        // inject IDbConnection?
        services.AddTransient<IDbConnection>((sp) => 
            new SqlConnection(this.Configuration.GetConnectionString("appDbConnection"))
        );

        // ...
    }
}

Use IDbConnection within the repository?

public interface ISpecificationRepository
{
    Specification GetById(int specificationId);
}

public SpecificationRepository : ISpecificationRepository
{
    private readonly IDbConnection _dbConnection;

    public SpecificationRepository(IDbConnection dbConnection)
    {
        _dbConnection = dbConnection;
    }

    public Specification GetById(int specificationId)
    {
        const string sql = @"SELECT * FROM [YOUR_TABLE]
                             WHERE Id = @specId;";

        return _dbConnection
            .QuerySingleOrDefault<Specification>(sql,
                new { specId = specificationId });
    }
}

Just need the connection string in a POCO?

You might use the Options Pattern.

  1. Define a class that exactly matches the JSON object structure in appsettings.json

    public class ConnectionStringConfig
    {
        public string AppDbConnection { get; set; }
    }
    
  2. Register that configuration on Startup

    public void ConfigureServices(IServiceCollection services)
    {
       // ...
    
       services.Configure<ConnectionStringConfig>(
           this.Configuration.GetSection("connectionStrings")
       );
    
       // ...
    }
    
  3. Receive the accessor in your POCO

    public class YourPoco
    {
        private readonly ConnectionStringConfig _connectionStringConfig;
    
        public YourPoco(IOptions<ConnectionStringConfig> configAccessor)
        {
            _connectionStringConfig = configAccessor.Value;
    
            // Your connection string value is here:
            // _connectionStringConfig.AppDbConnection;
        }
    }
    

Notes:

  1. See my sample codes on how to read values from appsettings.json both on Core 1.x and 2.0.
  2. See how I setup if you have more than 1 connection string.
David Liang
  • 20,385
  • 6
  • 44
  • 70
  • Thanks. Any chance of pasting the relevant bits in here for completeness? – niico May 24 '18 at 19:54
  • I need to read this value in a data access class - which is currently a POCO. All this DI stuff is great but right now I just need to access the connection string in a POCO - as before. How can I do that? – niico May 24 '18 at 20:04
  • and I guess throw 3 into a base class? – niico May 24 '18 at 22:20
  • In 1 what do you mean by 'exactly matches the connection strings structure'? – niico May 24 '18 at 22:29
  • My bad. I meant it needs to exactly match the JSON object structure. For example, if you have `customDbConnection` defined in the appsettings.json under "connectionStrings", in order to load values under that section into the configuration class (`ConnectionStringConfig` in my example), that configuration class needs to have a property `CustomDbConnection` defined. – David Liang May 24 '18 at 22:43
  • OK thanks - thats what I thought, just your example doesn't match your json so I wasn't sure. Should 'connectionStringConfig' be 'connectionStrings'? – niico May 24 '18 at 22:45
  • The name of that config class doesn't matter. It's just that its properties have to match the JSON structure of the section you're going to load. – David Liang May 24 '18 at 22:48
  • Thanks. Why is this so hard? I just want to get 1 line of text like I did from web config before and it requires all this?!?! Just seems like overkill. – niico May 24 '18 at 22:52
  • Hey you should thank those who developed this. It's clean and neat. If you need the configuration values more than 1 place, you just inject the configuration accessor there and it works. Hard? It depends on how you look at it. I mean you can build a huge application using just 1 project and put everything into it. Or you can separate things well into projects. Which one will go in a long way? There must be reasons why web form is deprecated. – David Liang May 24 '18 at 22:56
  • I was using MVC 5 - I can just get the connection string in 1 line of code in every data access class. I have no doubt this is a very good way of structuring it for large projects - but it still seems over the top. No doubt I'm wrong in 18 different ways. – niico May 24 '18 at 22:59
  • If you need the connection string in every data access class, you're probably doing it wrong (no offense as I am not the best architect out there) as it should just be loaded once on startup. Sure, for small projects you can just put everything together. You developed it quickly and it worked. But then if you mix the responsibilities of everything, it's hard to maintain. Which project doesn't need to maintain? Unless you build it once and forget about it. Not to mention the customer(s) will keep wanting new features / making existing features cooler. – David Liang May 24 '18 at 23:02
  • I have no issues maintaining it - there is 1 line getting the connection string from the web.config. I can change the connection string in the web config if required. No architecture is perfect and sure that could be improved but it's not an issue at all. – niico May 24 '18 at 23:04
  • Yes, if you want to do that, then just keep using MVC 5 with web.cofigs (and ConfigurationManager?). In .Net Core, web.configs are gone (until you publish) and replaced by .csproj files. Not to mention you can define appsettings for different environments. So much cleaner. – David Liang May 24 '18 at 23:07
  • I'd like to move to .net core - I guess I'll just have to do it this way then. It's just an unexpected learning curve at a bad time. Thanks again. – niico May 24 '18 at 23:10
  • You're welcome. There are so many things to learn after moving to .Net Core. This is just one of them. The built-in Dependency Injection system is another one. I mean you can use `new()` anywhere, plug in your fav. 3rd party DI library, or keep asking yourself why you have to use interfaces. But the benefits come when you need to do unit testings (I know... small projects don't need testings) and don't need to dispose instances. There are so many nice things.Net Core brings. Trust me. Once you master it, you don't want to go back to MVC 5 and web.config. – David Liang May 24 '18 at 23:16
  • Yeah DI was on my list - just didn't expect to have to do it right now in the middle of a deadline ;) – niico May 24 '18 at 23:18
  • Do you know of any simple solutions I can download that include DI in .net core 2 + Dapper or similar? They all seem to use EF. – niico May 24 '18 at 23:33
  • So how do I create an instance of the Poco from a controller now in the second example? The constructor is expecting a configAccessor parameter - where do I get that from? – niico May 24 '18 at 23:50
  • 1
    I can't do any detailed explanation here in the comment section. You need to read https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.0. – David Liang May 24 '18 at 23:55
  • You're not securing the sensitive data... if somebody gets his/her hands on the code then database access and maybe even server access is compromised... – victorvartan Nov 09 '18 at 20:18
  • To store app settings data safely during development, please visit https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/secure-net-microservices-web-applications/developer-app-secrets-storage – David Liang Nov 13 '18 at 01:28
3

Just put like shown below in appsettings.json.

"ConnectionStrings": {
    "DefaultConnection": "Data Source=;Initial Catalog=;Persist Security Info=True;User ID=; Password=;"
}

In Startup.cs fetch it as mentioned below:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

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

    public IConfigurationRoot Configuration { get; }
}

Use dependency injection to inject configuration in controller like mentioned below:

public class MyController : Controller
{
    private readonly IConfiguration _configuration;
    private string connectionString;

    public MyController(IConfiguration configuration) 
    {
        _configuration = configuration;

        connectionString = _configuration.GetConnectionString("DefaultConnection");
    }
}
Kirk Larkin
  • 84,915
  • 16
  • 214
  • 203
Riddhi
  • 201
  • 1
  • 10
  • My Startup constructor already has (IConfiguration configuration) in it. What effect will changing it to (IHostingEnvironment env) have? Any downsides? – niico May 24 '18 at 14:04
  • Or should I keep the other constructor too? – niico May 24 '18 at 14:16
  • Also do you really need to use IConfigurationRoot - rather than just IConfiguration? If so why? – niico May 24 '18 at 14:18
  • I also need to access the connectionstring from a Data class - not from a controller. I've tried making a POCO class for this with the same constructor as MyController but it doesn't work (connectionString is null) - what am I missing? How is the Controller getting the configuration passed to its constructor? – niico May 24 '18 at 14:55
  • 1
    Controller is getting it using dependency injection, if you use IConfiguration and initialize it in constructor like shown in above code, you will not get that error. – Riddhi Jul 20 '18 at 03:15
  • How are you **Configuration = builder.Build();** when **Configuration** is READ-ONLY? – Bill Roberts Jun 14 '19 at 21:25