Best Practices for Retrieving Connection Strings from appsettings.json in .NET Core 2.0

Nov 19, 2025 · Programming · 11 views · 7.8

Keywords: .NET Core | Connection String | Entity Framework Core | Configuration Management | Migrations

Abstract: This article provides an in-depth exploration of how to avoid hardcoding connection strings in .NET Core 2.0 applications, particularly when using Entity Framework Core migrations. By analyzing the implementation of the IDesignTimeDbContextFactory interface, it introduces methods for dynamically loading connection strings from the appsettings.json configuration file. The article includes complete code examples and configuration steps to help developers achieve centralized configuration management and code maintainability.

Problem Background and Challenges

In .NET Core 2.0 application development, Entity Framework Core migration functionality often requires the use of the IDesignTimeDbContextFactory<TContext> interface to create database context instances. The traditional approach involves hardcoding connection strings within the factory class, leading to configuration information being duplicated across multiple locations and violating the DRY (Don't Repeat Yourself) principle.

A typical hardcoded implementation is shown below:

public class ToDoContextFactory : IDesignTimeDbContextFactory<AppContext>
{
    public AppContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<AppContext>();
        builder.UseSqlServer("Server=localhost;Database=DbName;Trusted_Connection=True;MultipleActiveResultSets=true");
        return new AppContext(builder.Options);
    }
}

The obvious drawback of this approach is that the connection string appears in multiple locations within the application, increasing maintenance costs and posing risks in terms of security and configuration management.

Solution Implementation

To address the issue of configuration duplication, we can dynamically load the appsettings.json configuration file to retrieve the connection string. The following is the complete implementation solution:

Step 1: Configure the Factory Class

Modify the IDesignTimeDbContextFactory implementation to read the connection string from the configuration file:

public class ToDoContextFactory : IDesignTimeDbContextFactory<AppContext>
{
    public AppContext CreateDbContext(string[] args)
    {
        // Get the project root directory path
        string projectPath = AppDomain.CurrentDomain.BaseDirectory
            .Split(new String[] { @"bin\" }, StringSplitOptions.None)[0];
        
        // Build the configuration object
        IConfigurationRoot configuration = new ConfigurationBuilder()
            .SetBasePath(projectPath)
            .AddJsonFile("appsettings.json")
            .Build();
        
        // Retrieve the connection string
        string connectionString = configuration.GetConnectionString("DefaultConnection");
        
        // Configure database context options
        var builder = new DbContextOptionsBuilder<AppContext>();
        builder.UseSqlServer(connectionString);
        
        return new AppContext(builder.Options);
    }
}

Step 2: Configuration File Setup

Define the connection string in the appsettings.json file:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=YOURSERVERNAME;Database=YOURDATABASENAME;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Step 3: File Deployment and Verification

Ensure that the appsettings.json file is correctly copied to the output directory. Use the debugger to check the value of AppDomain.CurrentDomain.BaseDirectory to determine the correct file location. Additionally, ensure that the Microsoft.Extensions.Configuration.Json NuGet package is referenced in the project.

Technical Details Analysis

Configuration Building Process

The core of the configuration building process lies in the use of the ConfigurationBuilder class. By using the SetBasePath() method to set the base path for the configuration file and then the AddJsonFile() method to add the JSON configuration file, this approach ensures that the configuration file is correctly located and parsed during migration execution.

Path Resolution Mechanism

The key to path resolution is understanding the execution environment of the .NET Core application. During migration execution, the application may run in a subdirectory under the bin directory, so string splitting is necessary to obtain the project's root directory path. This mechanism ensures that the configuration file can be correctly found regardless of the application's execution environment.

Error Handling and Validation

In practical applications, appropriate error handling mechanisms should be added, such as checking whether the configuration file exists and whether the connection string is empty. This can be achieved by adding conditional checks and exception handling:

if (string.IsNullOrEmpty(connectionString))
{
    throw new InvalidOperationException("Connection string not found in configuration file");
}

Alternative Solutions Comparison

In addition to the above solution, there are several alternative methods:

OnConfiguring Method Approach

Another method is to override the OnConfiguring method in the database context class:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    IConfigurationRoot configuration = new ConfigurationBuilder()
        .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
        .AddJsonFile("appsettings.json")
        .Build();
    optionsBuilder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
}

While this method is feasible, it may be less flexible than the factory pattern in certain scenarios, especially when multiple database context configurations are needed.

Parameterized Constructor Attempt

Some developers have attempted to inject the configuration object by adding a parameterized constructor to the factory class:

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
    private IConfiguration config;
    
    public ApplicationDbContextFactory(IConfiguration config)
    {
        this.config = config;
    }
    
    public AppDbContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<AppDbContext>();
        builder.UseSqlServer(this.config["ConnectionString"]);
        return new AppDbContext(builder.Options);
    }
}

However, this approach results in a System.MissingMethodException because Entity Framework Core's design-time tools require the factory class to have a parameterless constructor.

Best Practices Recommendations

Based on practical project experience, we recommend the following best practices:

Environment-Specific Configuration

In real-world projects, different configuration files should be used for different environments (development, testing, production). This can be controlled via environment variables:

var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
    .SetBasePath(projectPath)
    .AddJsonFile("appsettings.json")
    .AddJsonFile($"appsettings.{environmentName}.json", optional: true)
    .Build();

Security Considerations

For production environments, sensitive information such as database passwords should not be stored directly in configuration files. It is recommended to use Azure Key Vault, environment variables, or other secure configuration storage solutions.

Performance Optimization

In scenarios where database contexts are created frequently, consider caching the configuration object to avoid repeated file read operations. However, be mindful of synchronization issues when configurations are updated.

Conclusion

By dynamically loading the appsettings.json configuration file to retrieve connection strings, we have successfully resolved the issue of duplicated configuration information in .NET Core 2.0. This approach not only improves code maintainability but also provides flexibility for configuration management across different environments. The key lies in correctly understanding the configuration building process and path resolution mechanisms, as well as avoiding common implementation pitfalls.

In practical applications, developers should choose the most suitable configuration management strategy based on specific requirements and always adhere to best practices for security and maintainability.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.