EF core unable to resolve environment variable using UseSqlServer with SpecFlow

Viewed 382

I am converting a BDD test project to the use EF core 5.x and SpecFlow 3.x + Specflow.Autofac. When executing the scenario the context is unable to find the connection string from the environmental variables.

Solution setup has the following structure

  • API (project)
    • appsettings.json (not the one that should be used for the specflow tests)
  • Business (project)
    • BusinessRegister.cs
  • Data (project)
    • MyContext.cs
    • DataRegister.cs
  • BddTest (project)
    • appsettings.json (has connection string for bdd testing for mycontext)
    • DependencyResolver.cs
    • various scenarios

The scenario dependencies are registered before all scenarios are executed;

public static class DependencyResolver
{
    private static IConfiguration config;

    [ScenarioDependencies]
    public static ContainerBuilder CreateContainerBuilder()
    {
        var containerBuilder = new ContainerBuilder();
        if (config == null)
        {
            string fullPath = System.Reflection.Assembly.GetAssembly(typeof(DependencyResolver)).Location;
            string theDirectory = Path.GetDirectoryName(fullPath);

            config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .SetBasePath(theDirectory)
                .AddEnvironmentVariables()
                .Build();
        }

        containerBuilder.RegisterInstance(config);
        containerBuilder.RegisterTypes(typeof(DependencyResolver).Assembly.GetTypes().Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))).ToArray()).SingleInstance(); 
        containerBuilder.RegisterModule(new DependencyRegister());
        containerBuilder.RegisterModule(new BusinessRegister());
        containerBuilder.RegisterModule(new DataRegister());
        return containerBuilder;
    }
}

The DataRegister registers MyContext;

public class DataRegister : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<MyContext>().AsSelf().InstancePerDependency();
    }
}

Inside MyContext it calls UseSqlServer to attempt to resolve a environment variable

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.EnableDetailedErrors();

    if (!optionsBuilder.IsConfigured)
    {
           optionsBuilder.UseSqlServer("Name=ConnectionStrings:MyContext");
    }
}

The appsettings.json located in the BddTest project

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.EntityFrameworkCore": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
    "MyContext": "--"
  }
}

When I attempt to run a scenario the application errors out when attempting to create a database connection inside of MyContext;

System.InvalidOperationException: 'A named connection string was used, but the name 'ConnectionStrings:MyContext' was not found in the application's configuration. Note that named connection strings are only supported when using 'IConfiguration' and a service provider, such as in a typical ASP.NET Core application.

How do I register my appsettings.json so that it is available inside the Data project and can be resolved when using optionsBuilder.UseSqlServer("Name=ConnectionStrings:MyContext");?

3 Answers

Try this:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.EnableDetailedErrors();

    if (!optionsBuilder.IsConfigured)
    {
       var config = ConfigurationManager.ConnectionStrings["MyContext"];
       var conn = new SqlConnection(config.ConnectionString);

       options.UseSqlServer(conn);
    }
}

To solve the issue I did something very similar to what @Matheus Dasuke was suggesting. Injected the IConfiguration then read out the values. This means I can get the values of the properties from environmental vars, user secrets, etc.

private static IConfiguration _configuration;

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

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
    optionsBuilder.EnableDetailedErrors();

    if (!optionsBuilder.IsConfigured) {
        var builder = new NpgsqlConnectionStringBuilder("Host=" +
            _configuration.GetConnectionString("Host")) {
            Password = _configuration["Password"],
                Username = _configuration["Username"],
                Database = _configuration["Database"],
        };
        optionsBuilder.UseNpgsql(builder.ConnectionString);
    }
}

Use:

_config.GetValue<string>("ConnectionStrings:MyContext")

Where _config is a DI IConfiguration injected in your constructor where OnConfiguring is.

Related