在 .Net Core 启动时如何根据环境动态选择连接字符串?

3
在ASP.Net MVC Core的StartUp类的Configure方法中,通过依赖注入传入"IHostingEnvironment env"。并且可以根据环境做出决策。例如,
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

在ConfigureServices方法中,我想要做类似于以下的操作来选择正确的连接字符串。 类似于:
        if (env.IsDevelopment())
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }
        else if (env.IsStaging())
        {
            // Add Staging Connection
        }
        else
        {
            // Add Prod Connection
        }

但是"IHostingEnvironment env"默认情况下不会传递给ConfigureServices方法。所以我修改了签名,从以下内容进行修改:

public void ConfigureServices(IServiceCollection services)

为了

public void ConfigureServices(IServiceCollection services, IHostingEnvironment env)

在ConfigureServices中添加以下内容:
        if (env.IsDevelopment())
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }
        else if (env.IsStaging())
        {
            // Add Staging Connection
        }
        else
        {
            // Add Prod Connection
        }

现在当我运行时,会出现以下错误信息:

“ConfigureServices方法必须是无参数或只有一个类型为IServiceCollection的参数。”

ConfigureServices()不会接受IHostingEnvironment变量。

但是"Startup.StartUp(IHostingEnvironment env)"可以。 我考虑添加一个StartUp类字段并将其设置为从Startup()中正确的环境,然后使用该字段来使ConfigureServices中的决策流程。但这似乎是一种不太好的解决办法。

我知道环境在.Net Core中是一种一等概念。 是否有一种方法可以直接从appsetting.json中完成这个操作?

实现此操作的最佳实践是什么?


可能是[如何在ASP.net Core中根据环境添加基于DbContext的内容]的重复问题(https://dev59.com/053ha4cB1Zd3GeqPWI11)。 - Sanket
4个回答

6
更新 就像Sam所说,Joe的解决方案虽然可行,但这是一种hack方法。从ASP.Net Core 1.1开始,使用不同的连接处理不同环境的正确方法是直接从appsettings.json文件中处理,而不是在ConfigureServices方法中检查环境。项目默认带有appsettings.jsonappsettings.Development.json文件(如果需要,可以手动创建一个不同的Staging文件,使用相同的命名约定)。
因此,在Joe的解决方案中,您将不需要在构造函数中设置环境的值,也不需要在ConfigurServices中使用它来检查托管环境。所以要摆脱:

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

public IHostingEnvironment environment { get; set; }
........
//还要摆脱if / else语句。

因此,您的ConfigureServices方法只会注入:
services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

现在,您的 appsettings.json 将包含以下内容:
  "ConnectionStrings": {
    "DefaultConnection": "//Value of your connection string for Production Env."
  }

你的 appsettings.Development.json 将会包含:

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

现在,如果您查看构造函数方法,变量builder已经加载了两个appsettings文件,根据您是在开发环境还是发布生产环境中运行项目,它将使用正确的文件,因此可以使用其中定义的设置。

1
值得一提的是,我们永远不应该将生产敏感数据存储在类似配置文件中。我知道这只是一个例子,但是...从来不知道人们会如何滥用这些东西:) 对于生产环境的配置,最好的方法始终是使用环境变量。 - jpgrassi

2

IHostingEnvironment被传递到Startup的构造函数中,您可以将其持久化到属性中,然后从ConfigureServices中使用它。

    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
    }

}

这对我来说似乎不是最佳实践,否则微软ASP.Net Core团队就会像在Configure()和Startup()中一样让您将IHostingEnvironment注入到ConfigureServices()中。这就是我在原始帖子中提到的hack,但我正在寻找更好的方法。我想我可能会采用这种方式,直到找到正确的解决方案。谢谢反馈。 - Sam

0

您还可以利用基于环境的启动类和方法来解决此问题。因此,您可以像这样做:

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

关于不同环境之间的共同服务,您可以将它们提取到一个私有方法中,并在Configure{Environment}Services上调用它。此外,考虑不要将生产连接字符串放入源代码控制中。您可以将其设置并作为机器上的环境变量进行读取。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接