2
votes

In the StartUp class's Configure method of ASP.Net MVC Core, "IHostingEnvironment env" is passed in via Dependency Injection. And decisions can be made based on the environment. For example,

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

In ConfigureServices, I want to do something like this to pick the correct Connection string. Something like:

        if (env.IsDevelopment())
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }
        else if (env.IsStaging())
        {
            // Add Staging Connection
        }
        else
        {
            // Add Prod Connection
        }

But "IHostingEnvironment env" is not passed in to the ConfigureServices method by default. So I modify the signature from:

public void ConfigureServices(IServiceCollection services)

to

public void ConfigureServices(IServiceCollection services, IHostingEnvironment env)

and in ConfigureServices put:

        if (env.IsDevelopment())
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }
        else if (env.IsStaging())
        {
            // Add Staging Connection
        }
        else
        {
            // Add Prod Connection
        }

So now when I run I get this error message:

"The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection."

ConfigureServices() won't take in the IHostingEnvironment var.

But "Startup.StartUp(IHostingEnvironment env)" does. I thought about adding a StartUp class field and setting it to the correct environment from Startup() and then using that field to make the decision flow in ConfigureServices. But this seems like a hack.

I know environment is a first class concept in .Net Core. Is there a way to do this straight from appsetting.json?

What is the best practice to accomplish this?

3

3 Answers

5
votes

Update Like Sam said, Joe's solution, though works fine, this is a hack. As of ASP.Net Core 1.1, the correct approach of using different connection for different environment is to handle it from the appsettings.json file itself rather then checking for the environment in the ConfigureServices method. The project by default comes with appsettings.json and appsettings.Development.json files (a different one for Staging can be created manually using the naming convention, if need be)

So in Joe's solution you will not need to set the value of the environment in the constructor nor need to use it to check hosting environment it in the ConfigurServices. So get rid of:

environment = env;
........

public IHostingEnvironment environment { get; set; }
........
//Also, get rid of if /else in the ConfigureServices method.

So your ConfigureServices method will just inject:

services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Now, your appsettings.json will have:

  "ConnectionStrings": {
    "DefaultConnection": "//Value of your connection string for Production Env."
  }

and your appsettings.Development.json will have:

"ConnectionStrings": {
        "DefaultConnection": "//Value of your connection string for Development Env."
      }

Now, if you look at the constructor method, var builder already loads both appsettings files and depending upon if you are running the project in development environment or published production environment, it will use the correct file and hence the settings defined in them.

2
votes

IHostingEnvrionment is passed into the constructor of Startup, from there you can persist it to a property and then use it from ConfigureSerivces

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

        // add this file name to your .gitignore file
        // so you can create it and use on your local dev machine
        // remember last config source added wins if it has the same settings
        builder.AddJsonFile("appsettings.dev.json", optional: true);
        builder.AddEnvironmentVariables();

        Configuration = builder.Build();

        environment = env;
    }

    public IHostingEnvironment environment { get; set; }

public void ConfigureServices(IServiceCollection services)
{

    if (envirnoment.IsDevelopment())
    {
        services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    }
    else if (envirnoment.IsStaging())
    {
        // Add Staging Connection
    }
    else
    {
        // Add Prod Connection
    }

}
0
votes

You can also solve this by taking leverage of the Environment based Startup class and methods. So, you could do something like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("ProductionConnection")));
}

public void ConfigureStagingServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("StagingConnection")));
}

public void ConfigureDevelopmentServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}

As far as common services between environments, you can extract them into a private method and just call it on Configure{Environment}Services. Also, consider not having the production connection string in source control. You can set it and read it as an Environment Variable on the machine.