Is the Configuration object in Asp.Net Core 2.0, i

2019-08-22 08:34发布

I have had no problem setting up classes in Class Libraries, setting them up in services in the Startup.cs to be injectable and having them work as expected.

In a test app I am working in. I have a data class library project plus my MVC app project (Core 2.0.1 and EF 2.0.1 and MVC6)

In Startup.cs

 // *If* you need access to generic IConfiguration this is **required**
        services.AddSingleton<IConfiguration>(Configuration);

        services.AddOptions();
        // Add our class objects so they can be injected

 services.Configure<TestAppServices.ConfigurationClasses.ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
        services.Configure<TestAppServices.ConfigurationClasses.LoggingSettings>(Configuration.GetSection("Logging"));
        services.Configure<TestAppServices.ConfigurationClasses.ApplicationIconUrlSettings>(Configuration.GetSection("ApplicationIconUrls"));
        services.Configure<TestAppServices.ConfigurationClasses.DatabaseConfigurationSettings>(Configuration.GetSection("ConnectionStrings"));
        services.Configure<TestAppServices.ConfigurationClasses.SmsSettings>(Configuration.GetSection("SmsSettings"));

I can access all the settings in the individual classes that are set up but, the following doesn't work.

private static IConfiguration _configuration { get; set; }
    private const string AppSettingsConnectionsHeader = "ConnectionStrings";

    public DatabaseHelpers(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public static ConnectionStrings GetConnections()
    {
        var cs = new ConnectionStrings();
        _configuration.GetSection(AppSettingsConnectionsHeader).Bind(cs);
        return cs;
    }

I am getting the usual error

System.NullReferenceException: Object reference not set to an instance of an object. at TestAppServices.DatabaseHelpers.GetConnections()

_configuration is null, therefore it is not getting injected.

The GetConnections method is being called from a controller like so

 var cs = DatabaseHelpers.GetConnections();

Is there a special way of setting up the actual Configuration object so it is injectable in class library projects?

Update:

Per serpent5's answer I reconfigured my class library project to the following

public static class ConnectionStrings
{
    public static string DefaultConnection => "DefaultConnection";
    public static string TestingConnection => "TestingConnection";
    public static string StagingConnection => "StagingConnection";
    public static string ProductionConnection => "ProductionConnection";
    public static string DevelopmentConnection => "DevelopmentConnection";       
}

public class ConfigurationSettings
{
    private readonly IConfiguration _configuration;

    public ConfigurationSettings(IConfiguration config)
    {
        _configuration = config;
    }

    public DatabaseConfigurationSettings GetConnectionStringFor(DatabaseIsFor dbfor)
    {
        var dbcs = new DatabaseConfigurationSettings();
        switch (dbfor)
        {
            case DatabaseIsFor.Development:
                if (!string.IsNullOrEmpty(_configuration.GetConnectionString(ConnectionStrings.DevelopmentConnection)))
                {
                    dbcs.CompleteConnectionString = _configuration.GetConnectionString(ConnectionStrings.DevelopmentConnection);
                    dbcs.ConnectionName = ConnectionStrings.DevelopmentConnection;  //== _connections.Value.DevelopmentConnection.ToString();
                    return dbcs;
                }
                break;

            case DatabaseIsFor.Testing:
                if (!string.IsNullOrEmpty(_configuration.GetConnectionString(ConnectionStrings.TestingConnection)))
                {
                    dbcs.CompleteConnectionString = _configuration.GetConnectionString(ConnectionStrings.TestingConnection);
                    dbcs.ConnectionName = ConnectionStrings.TestingConnection;  //== _connections.Value.TestingConnection.ToString();
                    return dbcs;
                }
                break;

            case DatabaseIsFor.Staging:
                if (!string.IsNullOrEmpty(_configuration.GetConnectionString(ConnectionStrings.StagingConnection)))
                {
                    dbcs.CompleteConnectionString = _configuration.GetConnectionString(ConnectionStrings.StagingConnection);
                    dbcs.ConnectionName = ConnectionStrings.StagingConnection;  //== _connections.Value.TestingConnection.ToString();
                    return dbcs;
                }
                break;

            case DatabaseIsFor.Production:
                if (!string.IsNullOrEmpty(_configuration.GetConnectionString(ConnectionStrings.ProductionConnection)))
                {
                    dbcs.CompleteConnectionString = _configuration.GetConnectionString(ConnectionStrings.ProductionConnection);
                    dbcs.ConnectionName = ConnectionStrings.ProductionConnection;  //== _connections.Value.TestingConnection.ToString();
                    return dbcs;
                }
                break;

        }
        dbcs.CompleteConnectionString = _configuration.GetConnectionString(ConnectionStrings.DefaultConnection);
        dbcs.ConnectionName = ConnectionStrings.DefaultConnection;  //== _connections.Value.DefaultConnection.ToString();
        return dbcs;
    }
}

And I use it in the controller as follows

public class HomeController : Controller
{
    private IConfiguration Configuration { get; set; }

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

    public IActionResult Index()
    {
       // test view to check that this works
        var cs = new ConfigurationSettings(Configuration);            
        var dbcs = cs.GetConnectionStringFor(DatabaseIsFor.Production);

        var svm = SettingsViewModel(dbcs);
        return View(svm);
    }
}

And it works perfectly fine, Except I don't understand why I should have to pass Configuration when newing up ConfigurationSettings. Shouldn't that already be available there since I added it in Startup.cs ConfigureServices?

// Access to generic IConfiguration 
services.AddSingleton(Configuration);

1条回答
虎瘦雄心在
2楼-- · 2019-08-22 08:55

You can't use Constructor Injection if you're not going to instantiate an instance of your class. In the code you've provided, you've set up DatabaseHelpers to have an instance of IConfiguration injected via its constructor, but the class itself does not take part in Dependency Injection and therefore its constructor never needs to be called.

One workaround for your particular example would be to explicitly initialize your static class in some way. You could add a new method to DatabaseHelpers, something like this:

public static Initialize(IConfiguration configuration)
{
    _configuration = configuration;
}

You'd then just set this up somewhere at app startup - perhaps in ConfigureServices. e.g.:

services.AddSingleton<IConfiguration>(Configuration);
DatabaseHelpers.Initialize(Configuration);

Although this a technical workaround for your problem, I'd encourage you to consider whether using a static helper for this is the best option. I'm assuming that your actual implementation of DatabaseHelpers is more complex than what you've shown here and that you can't just add an instance of ConnectionStrings to your Dependency Injection service collection and use that instead.

UPDATE: Dependency Injection is not actually magic, it's just a pattern. It works well when you delegate the responsibility of creating classes to some other entity (generally it's the controller activation process in the MVC world), but it can't work when you take control of creation yourself. In your updated example, you have taken full responsibility for the creation of ConfigurationSettings; therefore, you must provide its constructor arguments.

If you don't want to handle this yourself, you can add ConfigurationSettings to your collection of services and let the DI container the resolve the dependent Configuration for you.

查看更多
登录 后发表回答