Passing application's connection string down t

2019-01-17 11:14发布

问题:

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

回答1:

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;
    }
}


回答2:

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.



回答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.



回答4:

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.



回答5:

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.



回答6:

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.



回答7:

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; }

}