ASP.NET Core 实战-11.配置 ASP.NET Core 应用程序
在本书的第 1 部分中,您了解了启动和运行 ASP.NET Core 应用程序的基础知识,以及如何使用 MVC 设计模式来创建传统的 Web 应用程序或 Web API。 一旦你开始构建真正的应用程序,你会很快发现你想在部署时调整各种设置,而不必重新编译你的应用程序。 本章介绍如何在 ASP.NET Core 中使用配置来实现这一点。
我知道。 配置听起来很无聊,对吧? 但我不得不承认,配置模型是 ASP.NET Core 中我最喜欢的部分之一。 它比以前的 ASP.NET 版本更易于使用且优雅得多。 在第 11.3 节中,您将学习如何从大量来源(JSON 文件、环境变量和命令行参数)加载值,并将它们组合成一个统一的配置对象。
最重要的是,ASP.NET Core 提供了轻松将此配置绑定到强类型选项对象的能力。 这些是从配置对象填充的简单 POCO 类,您可以将其注入到您的服务中,正如您将在第 11.4 节中看到的那样。 这使您可以很好地封装应用程序中不同功能的设置。
在本章的最后一部分,您将了解 ASP.NET Core 托管环境。 您通常希望您的应用程序在不同情况下以不同方式运行,例如在开发人员计算机上运行时与将其部署到生产服务器时相比。 这些不同的情况称为环境。 通过让应用知道它在哪个环境中运行,它可以加载不同的配置并相应地改变其行为。
在我们开始之前,让我们回到基础:什么是配置,我们为什么需要它,以及 ASP.NET Core 如何处理这些需求?
介绍 ASP.NET Core 配置模型
在本节中,我将简要介绍配置的含义以及您可以在 ASP.NET Core 应用程序中使用它的用途。 配置是提供给应用程序的一组外部参数,以某种方式控制应用程序的行为。 它通常包含应用程序将在运行时加载的设置和机密的混合。
定义 设置是更改应用程序行为的任何值。机密是包含敏感数据的特殊类型的设置,例如密码、第三方服务的 API 密钥或连接字符串。
在我们开始之前,一个明显的问题是考虑为什么需要配置应用程序,以及需要配置哪些类型的东西。 您通常应该将任何您认为是设置或机密的内容从应用程序代码中移出。 这样,您可以在部署时轻松更改这些值,而无需重新编译您的应用程序。
例如,您可能有一个显示实体店位置的应用程序。 您可以设置与存储商店详细信息的数据库的连接字符串,还可以设置诸如在地图上显示的默认位置、要使用的默认缩放级别以及用于访问 Google Maps API,如图 11.1 所示。 将这些设置和机密存储在已编译代码之外是一种很好的做法,因为这样可以轻松调整它们而无需重新编译代码。
这还有一个安全方面。 您不想将 API 密钥或密码等秘密值硬编码到您的代码中,因为它们可以提交到源代码控制并公开可用。 甚至可以提取已编译应用程序中嵌入的值,因此最好尽可能将它们外部化。
几乎每个 Web 框架都提供了一种加载配置的机制,之前的 ASP.NET 版本也不例外。 它使用 web.config 文件中的
图 11.1 您可以在配置中存储默认地图位置、缩放级别和映射 API Key,并在运行时加载它们。 在配置中和代码之外保留 API 密钥等秘密非常重要。 |
---|
ASP.NET Core 为您提供了全新的体验。 在最基本的层面上,您仍然将键值对指定为字符串,但您现在可以从多个来源加载它们,而不是从单个文件中获取这些值。 您可以从文件中加载值,但它们现在可以是您喜欢的任何格式:JSON、XML、YAML 等。 最重要的是,您可以从环境变量、命令行参数、数据库或远程服务加载值。 或者您可以创建自己的自定义配置提供程序。
定义 ASP.NET Core 使用配置提供程序从各种来源加载键值对。 应用程序可以使用许多不同的配置提供程序。
ASP.NET Core 配置模型也有覆盖设置的概念。 每个配置提供程序都可以定义自己的设置,也可以覆盖以前提供程序的设置。 您将在 11.3 节中看到这个非常有用的功能。
ASP.NET Core 可以轻松地将这些定义为字符串的键值对绑定到您在代码中定义的 POCO 设置类。 这种强类型配置模型使得围绕给定特征对设置进行逻辑分组变得容易,非常适合单元测试。
在我们深入了解从提供程序加载配置的细节之前,我们将退后一步,看看这个过程发生在哪里——在 HostBuilder 内部。 对于使用默认模板构建的 ASP.NET Core 5.0 应用程序,这始终位于 Program.cs 中的 Host.CreateDefaultBuilder() 方法中。
使用 CreateDefaultBuilder 配置您的应用程序
正如您在第 2 章中看到的,ASP.NET Core 5.0 中的默认模板使用 CreateDefaultBuilder 方法。 这是一个固执己见的辅助方法,可为您的应用设置许多默认值。 在本节中,我们将查看此方法的内部,以查看它配置的所有内容以及它们的用途。
清单 11.1 使用 CreateDefaultBuilder 设置配置
public class Program
{
public static void Main(string[] args)
{
//应用程序的入口点创建 IHostBuilder、构建IHost 并调用 Run。
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) //CreateDefaultBuilder 设置了许多默认值,包括配置
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
在第 2 章中,我忽略了这种方法,因为您很少需要为简单的应用程序更改它。 但是随着应用程序的增长,如果您想更改应用程序的配置加载方式,您可能会发现需要将其拆分。
此清单显示了 CreateDefaultBuilder 方法的概述,因此您可以了解 HostBuilder 是如何构造的。
清单 11.2 Host.CreateDefaultBuilder 方法
public static IHostBuilder CreateDefaultBuilder(string[] args)
{
//创建 HostBuilder 的实例
var builder = new HostBuilder()
//内容根定义了可以找到配置文件的目录。
.UseContentRoot(Directory.GetCurrentDirectory())
//配置托管设置,例如确定托管环境
.ConfigureHostConfiguration(config =>
{
// Configuration provider setup
})
//配置应用程序设置,本章的主题
.ConfigureAppConfiguration((hostingContext, config) =>
{
// Configuration provider setup
})
//设置日志记录基础设施
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(
hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
//配置 DI 容器,可选择启用验证设置
.UseDefaultServiceProvider((context, options) =>
{
var isDevelopment = context.HostingEnvironment
.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
//通过在调用 Build() 之前调用额外的方法返回 HostBuilder以进行进一步的配置
return builder;
}
在 HostBuilder 上调用的第一个方法是 UseContentRoot。 这告诉应用程序它可以在哪个目录中找到以后需要的任何配置或查看文件。 这通常是应用程序运行所在的文件夹,因此调用 GetCurrentDirectory。
提示 ContentRoot 不是您存储浏览器可以直接访问的静态文件的地方——它是 WebRoot,通常是 wwwroot。
ConfigureHostingConfiguration() 方法是您的应用程序确定它当前运行在哪个 HostingEnvironment 的地方。框架默认查找环境变量和命令行参数,以确定它是在开发环境还是生产环境中运行。 您将在第 11.5 节中了解有关托管环境的更多信息。
您可以在 ConfigureLogging 中为您的应用程序指定日志记录设置。我们将在第 17 章详细介绍日志记录; 现在,知道 CreateDefaultBuilder 为您设置就足够了。
CreateDefaultBuilder UseDefaultServiceProvider 中的最后一个方法调用将您的应用配置为使用内置的 DI 容器。 它还根据当前的 HostingEnvironment 设置 ValidateScopes 和 ValidateOnBuild 选项。 在开发环境中运行应用程序时,应用程序将自动检查捕获的依赖项,您在第 10 章中了解了这一点。
ConfigureAppConfiguration() 方法是11.3 节的重点。 您可以在此处加载应用程序的设置和机密,无论它们是在 JSON 文件、环境变量还是命令行参数中。 在下一节中,您将看到如何使用此方法使用 ASP.NET Core ConfigurationBuilder 从各种配置提供程序加载配置值。
为您的应用构建配置对象
在本节中,我们将深入了解配置系统。 您将学习如何从多个来源加载设置,如何将它们存储在 ASP.NET Core 内部,以及设置如何覆盖其他值以提供配置“层”。 您还将学习如何安全地存储秘密,同时确保它们在您运行应用程序时仍然可用。
在 11.2 节中,您看到了如何使用 CreateDefaultBuilder 方法来创建 IHostBuilder 的实例。 IHostBuilder 负责设置有关您的应用程序的许多内容,包括 ConfigureAppConfiguration 方法中的配置系统。 此方法传递一个 ConfigurationBuilder 的实例,该实例用于定义您的应用程序的配置。
ASP.NET Core 配置模型以两个主要结构为中心:ConfigurationBuilder 和 IConfiguration。
注 ConfigurationBuilder 描述了如何为您的应用构建最终的配置表示,而 IConfiguration 自己保存配置值。
您可以通过向 ConfigureAppConfiguration 中的 ConfigurationBuilder 添加一些 IConfigurationProvider 来描述您的配置设置。 这些描述了如何从特定来源加载键值对; 例如,一个 JSON 文件或环境变量,如图 11.2 所示。 在 ConfigurationBuilder 上调用 Build() 会查询每个提供程序以获取它们包含的值,以创建 IConfigurationRoot 实例。
注 调用 Build() 创建一个 IConfigurationRoot 实例,该实例实现了 IConfiguration。 您通常会在代码中使用 IConfiguration 接口。
ASP.NET Core 附带用于从常见位置加载数据的配置提供程序:
- JSON files
- XML files
- Environment variables
- Command-line arguments
- INI files
如果这些不符合您的要求,您可以在 GitHub 和 NuGet 上找到大量替代方案,并且创建自己的自定义提供程序并不难。 例如,您可以使用官方的 Azure Key Vault 提供程序 NuGet 包或我编写的 YAML 文件提供程序。
在许多情况下,默认提供程序就足够了。 特别是,大多数模板都以 appsettings.json 文件开头,其中包含各种设置,具体取决于您选择的模板。 下面的清单显示了 ASP.NET Core 5.0 Web 应用模板在没有身份验证的情况下生成的默认文件。
清单 11.3 由 ASP.NET Core Web 模板创建的默认 appsettings.json 文件
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
如您所见,此文件主要包含控制日志记录的设置,但您也可以在此处为您的应用添加其他配置。
警告 不要在此文件中存储敏感值,例如密码、API 密钥或连接字符串。 您将在第 11.3.3 节中看到如何安全地存储它们。
添加您自己的配置值涉及向 JSON 添加一个键值对。通过为相关设置创建一个基础对象来“namespace”您的设置是一个好主意,如此处所示的 MapSettings 对象。
清单 11.4 向 appsettings.json 文件添加额外的配置值
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"MapSettings": { #将所有配置嵌套MapSettings键下。
"DefaultZoomLevel": 9,# 值可以是 JSON 文件中的数字,但在读取时会转换为字符串。
"DefaultLocation": { #您可以创建深度嵌套的结构来更好地组织您的配置值。
"latitude": 50.500,
"longitude": -4.000
}
}
}
我已将新配置嵌套在 MapSettings 父键中; 这将创建一个“section”,稍后在将您的值绑定到 POCO 对象时将很有用。 我还将纬度和经度键嵌套在 DefaultLocation 键下。 您可以创建任何您喜欢的值结构; 配置提供者会很好地阅读它们。 此外,您可以将它们存储为任何数据类型(在本例中为数字),但请注意,提供程序将在内部将它们读取并存储为字符串。
提示 配置键在您的应用程序中不区分大小写,因此在从键区分大小写的提供程序加载时请记住这一点。
现在您已经有了一个配置文件,是时候让您的应用使用 ConfigurationBuilder 加载它了。 为此,我们将返回到 Program.cs 中 HostBuilder 公开的 ConfigureAppConfiguration() 方法。
在 Program.cs 中添加配置提供程序
ASP.NET Core 中的默认模板使用 CreateDefaultBuilder 辅助方法为您的应用程序引导 HostBuilder,如您在第 11.2 节中所见。 作为此配置的一部分,CreateDefaultBuilder 方法调用 ConfigureAppConfiguration 并设置许多默认配置提供程序,我们将在本章中更详细地了解它们:
- JSON 文件提供程序 - 从名为 appsettings.json 的可选 JSON 文件加载设置。 它还从名为 appsettings.ENVIRONMENT.json 的可选环境特定 JSON 文件加载设置。 我将在 11.5 节展示如何使用特定于环境的文件。
- 用户机密 - 加载在开发过程中安全存储的机密。
- 环境变量——加载环境变量作为配置变量。 这些非常适合在生产中存储秘密。
- 命令行参数——在运行应用程序时使用作为参数传递的值。
使用默认构建器会将您绑定到此默认集,但默认构建器是可选的。 如果您想使用不同的配置提供程序,您可以创建自己的 HostBuilder 实例。 如果您采用这种方法,您将需要设置 CreateHostBuilder 所做的一切:日志记录、托管配置、服务提供商配置以及您的应用程序配置。
另一种方法是通过添加对 ConfigureAppConfiguration 的额外调用来添加其他配置提供程序,如下面的清单所示。 这允许您在 CreateHostBuilder 添加的提供程序之上添加额外的提供程序。 在以下清单中,您明确清除了默认提供程序,这使您可以完全自定义从何处加载配置,而无需替换 CreateHostBuilder 为日志记录等添加的默认值。
清单 11.5 使用自定义 HostBuilder 加载 appsettings.json
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
//HostBuilder增加配置设置功能
.ConfigureAppConfiguration(AddAppConfiguration)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
//HostBuilder 提供了一个宿主上下文和一个ConfigurationBuilder 实例。
public static void AddAppConfiguration(
HostBuilderContext hostingContext,
IConfigurationBuilder config)
{
//清除 CreateDefaultBuilder 中默认配置的提供程序
config.Sources.Clear();
//添加 JSON 配置提供程序,提供配置文件的文件名
config.AddJsonFile("appsettings.json", optional: true);
}
}
提示 在清单 11.5 中,我将配置提取到静态辅助方法 AddAppConfiguration,但您也可以将这个内联作为 lambda 方法提供。
HostBuilder 在调用 ConfigureAppConfiguration 方法之前创建一个 ConfigurationBuilder 实例。 您需要做的就是为您的应用程序添加配置提供程序。
在此示例中,您通过调用 AddJsonFile 扩展方法并提供文件名添加了一个 JSON 配置提供程序。 您还将 optional 的值设置为 true。 这告诉配置提供者跳过它在运行时找不到的文件,而不是抛出 FileNotFoundException。 请注意,扩展方法此时只是注册了提供者; 它还没有尝试加载文件。
就是这样! HostBuilder 实例负责调用 Build(),生成 IConfiguration,代表您的配置对象。 然后将其注册到 DI 容器中,因此您可以将其注入到您的类中。 您通常会将其注入到 Startup 类的构造函数中,因此您可以在 Configure 和 ConfigureServices 方法中使用它:
public class Startup
{
public Startup(IConfiguration config)
{
Configuration = config;
}
public IConfiguration Configuration { get; }
}
注 ConfigurationBuilder 创建一个 IConfigurationRoot 实例,该实例实现 IConfiguration。 这在 DI 容器中注册为 IConfiguration,而不是 IConfigurationRoot。 IConfiguration 是您可以注入到 Startup 构造函数中的少数东西之一。
此时,在 Startup 构造函数的末尾,您有一个完全加载的配置对象。 但是你能用它做什么呢? IConfiguration 将配置存储为一组键值字符串对。 您可以使用标准字典语法通过其键访问任何值。 例如,您可以使用
var zoomLevel = Configuration["MapSettings:DefaultZoomLevel"];
检索为您的应用程序配置的缩放级别。 请注意,我使用冒号 (😃 来指定单独的部分。 同样,要检索纬度键,您可以使用
Configuration["MapSettings:DefaultLocation:Latitude"];
注意 如果请求的配置键不存在,您将获得空值。
您还可以使用 GetSection(section) 方法获取配置的整个部分,该方法返回一个实现 IConfiguration 的 IConfigurationSection。 这会抓取一大块配置并重置命名空间。 获取纬度键的另一种方法是
Configuration.GetSection("MapSettings")["DefaultLocation:Latitude"];
在定义应用程序时,在 Startup 的 ConfigureServices 和 Configure 方法中访问这样的设置值很有用。 例如,在设置应用程序以连接到数据库时,您通常会从 Configuration 对象加载连接字符串(当我们查看 Entity Framework Core 时,您将在下一章中看到一个具体示例)。
如果您需要从 Startup 以外的类访问这样的配置对象,您可以使用 DI 将其作为服务构造函数中的依赖项。 但是像这样使用字符串键访问配置并不是特别方便; 您应该尝试使用强类型配置,正如您将在第 11.4 节中看到的那样。
到目前为止,从 JSON 文件加载设置可能有点令人费解和普通,我承认,确实如此。 当您有多个提供程序时,ASP.NET Core 配置系统的亮点就在于此。
使用多个提供程序覆盖配置值
您已经看到 ASP.NET Core 使用 Builder 模式来构造配置对象,但到目前为止您只配置了一个提供程序。 添加提供者时,重要的是要考虑添加它们的顺序,因为它定义了将配置值添加到底层字典的顺序。 来自较晚提供者的配置值将覆盖来自较早提供者的具有相同键的值。
注意 这需要重复:将配置提供程序添加到 ConfigurationBuilder 的顺序很重要。 后面的配置提供者可以覆盖早期提供者的值。
将配置提供程序视为将配置值的“层”添加到堆栈中,其中每一层可能与下面的部分或全部层重叠,如图 11.3 所示。 当您调用 Build() 时,ConfigurationBuilder 将这些层合并为一个,以创建存储在 IConfiguration 中的最终配置值集。
更新您的代码以从三个不同的配置提供程序(两个 JSON 提供程序和一个环境变量提供程序)加载配置,方法是将它们添加到 ConfigurationBuilder。 为简洁起见,我只在清单 11.6 中展示了 AddAppConfiguration 方法。
图 11.3 每个配置提供程序都会向 ConfigurationBuilder 添加一个值“layer”。调用 Build() 会折叠该配置。 以后的提供者将使用与早期提供者相同的键覆盖配置值。 |
---|
清单 11.6 从 Startup.cs 中的多个提供程序加载
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
HostBuilderContext hostingContext,
IConfigurationBuilder config)
{
config.Sources.Clear();
config
//在 appsettings.json 文件之前从不同的 JSON配置文件加载配置
.AddJsonFile("sharedSettings.json", optional: true)
.AddJsonFile("appsettings.json", optional: true)
//将机器的环境变量添加为配置提供程序
.AddEnvironmentVariables();
}
}
这种分层设计可以用于许多事情。 从根本上说,它允许您将来自多个不同来源的配置值聚合到一个单一的、有凝聚力的对象中。 为了巩固这一点,请考虑图 11.4 中的配置值。
每个提供程序中的大多数设置都是唯一的,并添加到最终的 IConfiguration。 但是“MyAppConnString”键同时出现在 appsettings.json 和环境变量中。 因为环境变量提供程序是在 JSON 提供程序之后添加的,所以在 IConfiguration 中使用了环境变量配置值。
整理来自多个提供者的配置的能力本身就是一个方便的特性,但这种设计在处理敏感配置值(例如连接字符串和密码)时特别有用。 下一节将展示如何在本地开发机器和生产服务器上处理这个问题。
图 11.4 最终的 IConfiguration 包括来自每个提供者的值。 appsettings.json 和环境变量都包含 MyAppConnString 键。 由于稍后添加环境变量,因此使用该配置值。 |
---|
安全地存储配置机密
一旦您构建了一个重要的应用程序,您就会发现您需要将某种敏感数据作为设置存储在某处。 例如,这可以是密码、连接字符串或远程服务的 API 密钥。
将这些值存储在 appsettings.json 中通常是一个坏主意,因为您永远不应该将秘密提交给源代码控制; 人们提交给 GitHub 的 API 密钥的数量是可怕的! 相反,最好将这些值存储在项目文件夹之外,这样它们就不会被意外提交。
您可以通过几种方式做到这一点,但最简单和最常用的方法是使用环境变量作为生产服务器上的密钥和本地用户密钥。
这两种方法都不是真正安全的,因为它们不会以加密格式存储值。 如果您的机器遭到入侵,攻击者将能够读取存储的值,因为它们是以明文形式存储的。 它们旨在帮助您避免将秘密提交给源代码控制。
无论您使用哪种方法来存储应用程序机密,如果可能,请确保您没有将它们存储在源代码管理中。 即使是私有存储库也可能不会永远保持私有,因此最好谨慎行事。
在生产环境变量中存储秘密
您可以使用 AddEnvironmentVariables 扩展方法添加环境变量配置提供程序,如清单 11.6 中所示。 这会将您机器上的所有环境变量作为键值对添加到配置对象中。
注意 如您在第 11.2 节中所见,默认情况下会在 CreateDefaultBuilder 中添加环境变量提供程序。
您可以使用冒号 (😃 或双下划线 (__) 在环境变量中创建通常在 JSON 文件中看到的相同分层部分来划分部分; 例如,MapSettings:MaxNumberOfPoints 或 MapSettings__MaxNumber-OfPoints。
提示 某些环境,例如 Linux,不允许在环境变量中使用冒号。 您必须在这些环境中使用双下划线方法。 环境变量中的双下划线在导入 IConfiguration 对象时会转换为冒号。 从应用程序中的 IConfiguration 检索值时,应始终使用冒号。
当您将应用程序发布到独立环境(例如专用服务器、Azure 或 Docker 容器)时,环境变量方法特别有用。 您可以在生产机器或 Docker 容器上设置环境变量,提供程序将在运行时读取它们,覆盖 appsettings.json 文件中指定的默认值。
对于开发机器,环境变量不太有用,因为您的所有应用程序都将使用相同的值。 例如,如果您设置 ConnectionStrings__DefaultConnection 环境变量,则会为您在本地运行的每个应用程序添加该环境变量。 这听起来比好处更麻烦!
对于开发场景,您可以使用 User Secrets Manager。 这有效地添加了每个应用程序的环境变量,因此您可以为每个应用程序设置不同的设置,但将它们存储在与应用程序本身不同的位置。
与开发中的用户机密管理器一起存储机密
User Secrets 背后的想法是简化在应用程序的项目树之外存储每个应用程序的秘密。 这类似于环境变量,但您为每个应用程序使用唯一的密钥来隔离秘密。
警告 这些秘密没有加密,所以它们不应该被认为是安全的。尽管如此,这是对将它们存储在项目文件夹中的改进。
设置 User Secrets 比使用环境变量需要更多的努力,因为您需要配置一个工具来读取和写入它们,添加 User Secrets 配置提供程序,并为您的应用程序定义一个唯一密钥:
默认情况下,ASP.NET Core 包含 User Secrets 提供程序。 .NET SDK 还包括一个用于从命令行处理机密的全局工具。
如果您使用的是 Visual Studio,请右键单击您的项目并选择 Manage User Secrets。 这将打开一个 secrets.json 文件的编辑器,您可以在其中存储您的键值对,就像它是一个 appsettings.json 文件一样,如图 11.5 所示。
图 11.5 选择 Manage User Secrets 打开 User Secrets 应用程序的编辑器。 在本地开发应用程序时,您可以使用此文件来存储机密。 它们存储在您的项目文件夹之外,因此它们不会被意外提交到源代码管理。 |
---|
向 .csproj 文件添加唯一标识符。 当您单击 Manage User Secrets 时,Visual Studio 会自动执行此操作,但如果您使用的是命令行,则需要自己添加。 通常,您会使用唯一 ID,例如 GUID:
<PropertyGroup>
<UserSecretsId>96eb2a39-1ef9-4d8e-8b20-8e8bd14038aa</UserSecretsId>
</PropertyGroup>
如果您不使用 Visual Studio,则可以使用命令行添加用户机密
dotnet user-secrets set "MapSettings:GoogleMapsApiKey" F5RJT9GFHKR7
或者您可以使用您喜欢的编辑器直接编辑 secret.json 文件。 此文件的确切位置取决于您的操作系统,并且可能会有所不同。 检查文档以获取详细信息。
唷,这是很多设置,如果你正在自定义 HostBuilder,你还没有完成! 您需要使用 ConfigureAppConfiguration 方法中的 AddUserSecrets 扩展方法更新您的应用程序以在运行时加载用户机密:
if(env.IsDevelopment())
{
configBuilder.AddUserSecrets<Startup>();
}
注意 您应该只在开发中使用 User Secrets 提供程序,而不是在生产中,因此在前面的代码片段中,您有条件地将提供程序添加到 ConfigurationBuilder。 如前所述,在生产中应使用环境变量或 Azure Key Vault。 如果您使用 Host.CreateDefaultBuilder(),默认情况下这一切都正确配置。
你就拥有了——在开发过程中将你的秘密安全地存储在你的项目文件夹之外。 这可能看起来有点矫枉过正,但如果您有任何您认为需要加载到配置中的远程敏感信息,我强烈建议您使用环境变量或用户机密。
几乎是时候将配置提供程序抛在后面了,但在此之前,我想向您展示 ASP.NET Core 配置系统的派对技巧:动态重新加载文件。
更改时重新加载配置值
除了安全性之外,每次想要调整值时不必重新编译应用程序是使用配置和设置的优势之一。 在以前版本的 ASP.NET 中,通过编辑 web.config 更改设置将导致您的应用程序必须重新启动。 这个节拍必须重新编译,但是在应用程序启动之前等待它可以服务请求有点拖累。
在 ASP.NET Core 中,您终于能够编辑文件并自动更新应用程序的配置,而无需重新编译或重新启动。
当您尝试调试生产中的应用程序时,您可能会发现这经常被引用的场景。 您通常将日志记录配置为多个级别之一:
- Error
- Warning
- Information
- Debug
这些设置中的每一个都比上一个更详细,但它也提供了更多的上下文。默认情况下,您可以将您的应用程序配置为仅在生产环境中记录警告和错误级别的日志,这样您就不会生成太多多余的日志条目。 相反,如果您尝试调试问题,则需要尽可能多的信息,因此您可能需要使用调试日志级别。
能够在运行时更改配置意味着您可以在遇到问题时轻松打开额外的日志,然后通过编辑 appsettings.json 文件将它们切换回来。
注意 重新加载通常仅适用于基于文件的配置提供程序,而不是环境变量或用户机密提供程序。
当您将任何基于文件的提供程序添加到您的 ConfigurationBuilder 时,您可以启用重新加载配置文件。 Add*File 扩展方法包括带有 reloadOnChange 参数的重载。 如果设置为 true,则应用程序将监视文件系统以了解文件的更改,并在需要时触发 IConfiguration 的完全重建。 此清单显示如何将配置重新加载添加到在 AddAppConfiguration 方法中加载的 appsettings.json 文件。
清单 11.7 文件更改时重新加载 appsettings.json
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
HostBuilderContext hostingContext,
IConfigurationBuilder config)
{
config.AddJsonFile(
"appsettings.json",
optional: true
//如果 appsettings.json 文件发生更改,将重建 IConfiguration。
reloadOnChange: true);
}
}
有了它,您对文件所做的任何更改都将反映在 IConfiguration 中。 但正如我在本章开头所说,IConfiguration 并不是在应用程序中传递设置的首选方式。 相反,正如您将在下一节中看到的,您应该支持强类型 POCO 对象。
使用带有选项模式的强类型设置
在本节中,您将了解强类型配置和选项模式。这是在 ASP.NET Core 中访问配置的首选方式。 通过使用强类型配置,您可以避免访问配置时出现拼写错误的问题。它还使类更易于测试,因为您可以使用简单的 POCO 对象进行配置,而不是依赖于 IConfiguration 抽象。
到目前为止,我展示的大多数示例都是关于如何将值放入 IConfiguration,而不是如何使用它们。 你已经看到你可以使用 Configuration["key"] 字典语法来访问一个键,但是像这样使用字符串键感觉很混乱并且容易出现拼写错误。
相反,ASP.NET Core 提倡使用强类型设置。 这些是您定义和创建的 POCO 对象,它们代表一小部分设置,范围仅限于应用程序中的单个功能。
以下清单显示了商店定位器组件的设置和自定义应用主页的显示设置。 它们使用“MapSettings”和“AppDisplaySettings”键分为两个不同的对象,对应于它们影响的应用程序的不同区域。
清单 11.8 在 appsettings.json 中将设置分成不同的对象
{
"MapSettings": {
"DefaultZoomLevel": 6,
"DefaultLocation": {
"latitude": 50.500,
"longitude": -4.000
}
},
"AppDisplaySettings": {
"Title": "Acme Store Locator",
"ShowCopyright": true
}
}
使主页设置在 Index.cshtml Razor 页面中可用的最简单方法是将 IConfiguration 注入 PageModel 并使用字典语法访问值:
public class IndexModel : PageModel
{
public IndexModel(IConfiguration config)
{
var title = config["HomePageSettings:Title"];
var showCopyright = bool.Parse(
config["HomePageSettings:ShowCopyright"]);
}
}
但你不想这样做; 我喜欢的弦太多了! 还有那个 bool.Parse?Yuk! 相反,您可以使用具有所有类型安全性和 IntelliSense 优点的自定义强类型对象。
清单 11.9 使用 IOptions
将强类型选项注入 PageModel
public class IndexModel: PageModel
{
//您可以使用 IOptions<> 包装器接口注入强类型选项类。
public IndexModel(IOptions<AppDisplaySettings> options)
{
//Value 属性公开 POCO 设置对象
AppDisplaySettings settings = options.Value;
//设置对象包含在运行时绑定到配置值的属性。
var title = settings.Title;
//binder 还可以将字符串值直接转换为原始类型。
bool showCopyright = settings.ShowCopyright;
}
}
ASP.NET Core 配置系统包括一个绑定器,它可以获取配置值的集合并将它们绑定到一个强类型对象,称为选项类。这类似于第 6 章中模型绑定的概念,其中请求值是 绑定到您的 POCO 绑定模型类。
本节展示如何设置配置值与 POCO 选项类的绑定,以及如何确保在底层配置值更改时重新加载。 我们还将了解您可以绑定的不同类型的对象。
介绍 IOptions 接口
ASP.NET Core 引入了强类型设置作为让配置代码遵守单一职责原则并允许将配置类作为显式依赖项注入的一种方式。 这样的设置也使测试更容易; 您可以创建 POCO 选项类的实例,而不必创建 IConfiguration 的实例来测试服务。
POCO 选项类的实例。例如,上一个示例中显示的 AppDisplaySettings 类可能很简单,只公开与主页相关的值:
public class AppDisplaySettings
{
public string Title { get; set; }
public bool ShowCopyright { get; set; }
}
您的选项类必须是非抽象的,并且具有公共无参数构造函数才能进行绑定。 绑定器将设置任何与配置值匹配的公共属性,您很快就会看到。
提示 您不仅限于字符串和布尔等原始类型; 您也可以使用嵌套的复杂类型。 选项系统会将部分绑定到复杂的属性。 有关示例,请参阅相关的源代码。
为了帮助将配置值绑定到您的自定义 POCO 选项类,ASP.NET Core 引入了 IOptions
清单 11.10 在 Startup.cs 中使用 Configure
配置选项类
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//将 MapSettings 部分绑定到 POCO 选项类 MapSettings
services.Configure<MapSettings>(
Configuration.GetSection("MapSettings"));
//将 AppDisplaySettings 部分绑定到 POCO 选项类 AppDisplaySettings
services.Configure<AppDisplaySettings>(
Configuration.GetSection("AppDisplaySettings"));
}
提示 您不必像清单 11.10 中那样对节和类使用相同的名称; 这只是我喜欢遵循的惯例。 使用此约定,您可以使用 nameof() 运算符来进一步减少拼写错误的机会,例如通过调用 GetSection(nameof(MapSettings))。
每次调用 Configure<T>
都会在内部设置以下一系列操作:
-
创建
ConfigureOptions<T>
的实例,指示应根据配置配置IOptions<T>
。 如果多次调用
Configure<T>
,将使用多个ConfigureOptions<T>
对象,所有这些对象都可以用于创建最终对象,这与IConfiguration
从多个层构建的方式非常相似。 -
每个
ConfigureOptions<T>
实例将IConfiguration
的一部分绑定到 T POCO 类的实例。 这会根据提供的 ConfigurationSection 中的键设置选项类上的任何公共属性。请记住,部分名称(清单 11.10 中的“MapSettings”)可以具有任何值; 它不必与您的选项类的名称相匹配。
-
IOptions<T>
接口在 DI 容器中注册为单例,最终绑定的 POCO 对象位于Value
属性中。
如您所见,这最后一步允许您通过注入 IOptions<T>
将选项类注入控制器和服务。 这使您可以对配置值进行封装的强类型访问。 没有更多的魔法弦,呜呼!
警告 如果您忘记调用
Configure<T>
并将IOptions<T>
注入到您的服务中,您将不会看到任何错误,但 T 选项类不会绑定到任何东西,并且只会在其中包含默认值特性。
T 选项类与 ConfigurationSection
的绑定发生在您第一次请求 IOptions<T>
时。 该对象在 DI 容器中注册为单例,因此它只绑定一次。
这个设置有一个问题:当使用 IOptions<T>
时,不能使用我在 11.3.4 节中描述的 reloadOnChange
参数来重新加载强类型选项类。 如果您编辑 appsettings.json
文件,IConfiguration
仍会重新加载,但它不会传播到您的选项类。
如果这看起来像是倒退了一步,甚至是交易破坏者,那么请不要担心。 IOptions<T>
有一个表亲 IOptionsSnapshot<T>
,用于这种场合。
使用 IOptionsSnapshot 重新加载强类型选项
在上一节中,您使用 IOptions
这通常不是问题(你不应该真的在实时生产服务器上修改文件),但如果你需要这个功能,你可以使用 IOptionsSnapshot
从概念上讲,IOptionsSnaphot
-
IOptions
- 实例在第一次需要时创建一次。 它始终包含对象实例首次创建时的配置。 -
IOptionsSnapshot
- 如果在创建最后一个实例后底层配置发生更改,则在需要时创建一个新实例。
IOptionsSnaphot
清单 11.11 使用 IOptionsSnapshot
注入可重载选项
public class IndexModel: PageModel
{
public IndexModel(
//如果基础配置值更改,IOptionsSnapshot<T>将更新。
IOptionsSnapshot<AppDisplaySettings> options)
{ //Value 属性公开POCO 设置对象,与IOptions<T> 相同。
//设置对象将在某个时候匹配配置值,而不是在第一次运行时。
AppDisplaySettings settings = options.Value;
var title = settings.Title;
}
}
每当您编辑设置文件并导致重新加载 IConfiguration 时,都会重新构建 IOptionsSnapshot
使用选项模式时的一个重要考虑因素是您的 POCO 选项类本身的设计。 这些通常是简单的属性集合,但有一些事情要记住,这样您就不会陷入调试绑定显然不起作用的原因。
为自动绑定设计选项类
我已经谈到了 POCO 类与 IOptions
第一个关键点是绑定器将使用反射创建选项类的实例,因此您的 POCO 选项类需要
- 非抽象
- 有一个默认(公共无参数)构造函数
如果您的类满足这两点,则绑定程序将遍历您的类的所有属性并绑定任何可以绑定的属性。 在最广泛的意义上,绑定器可以绑定任何属性
-
是否公开
-
有一个 getter——活页夹不会写 set-only 属性
-
有一个设置器,或者对于复杂类型,有一个非空值
-
不是索引器
下面的清单显示了一个广泛的选项类,其中包含大量不同类型的属性,其中一些是有效的绑定,而另一些则不是。
清单 11.12 一个包含绑定和非绑定属性的选项类
public class TestOptions
{
public string String { get; set; }
public int Integer { get; set; }
public SubClass Object { get; set; }
public SubClass ReadOnly { get; } = new SubClass();
public Dictionary<string, SubClass> Dictionary { get; set; }
public List<SubClass> List { get; set; }
public IDictionary<string, SubClass> IDictionary { get; set; }
public IEnumerable<SubClass> IEnumerable { get; set; }
public ICollection<SubClass> IEnumerable { get; }
= new List<SubClass>();
internal string NotPublic { get; set; }
public SubClass SetOnly { set => _setOnly = value; }
public SubClass NullReadOnly { get; } = null;
public SubClass NullPrivateSetter { get; private set; } = null;
public SubClass this[int i] {
get => _indexerList[i];
set => _indexerList[i] = value;
}
public List<SubClass> NullList { get; }
public Dictionary<int, SubClass> IntegerKeys { get; set; }
public IEnumerable<SubClass> ReadOnlyEnumerable { get; }
= new List<SubClass>();
public SubClass _setOnly = null;
private readonly List<SubClass> _indexerList
= new List<SubClass>();
public class SubClass
{
public string Value { get; set; }
}
}
如清单所示,绑定器通常支持集合——实现和接口。 如果集合属性已经初始化,它将使用它,但绑定器也可以为它们创建支持字段。 如果您的属性实现了以下任何类,则活页夹将创建一个适当类型的 List<> 作为支持对象:
- IReadOnlyList<>
- IReadOnlyCollection<>
- ICollection<>
- IEnumerable<>
警告 您不能绑定到已初始化的 IEnumerable<> 属性,因为基础类型不公开 Add 函数。 如果您将其初始值保留为空,则可以绑定到 IEnumerable<>。
同样,绑定器将创建一个 Dictionary<,> 作为具有字典接口的属性的支持字段,只要它们使用字符串键:
- IDictionary<string,>
- IReadOnlyDictionary<string,>
警告 你不能用非字符串键绑定字典,例如 int。有关绑定集合类型的示例,请参阅本书的相关源代码。
显然这里有很多细微差别,但如果你坚持前面例子中的简单案例,你会没事的。 请务必检查 JSON 文件中的拼写错误!
提示 选项模式最常用于将 POCO 类绑定到配置,但您也可以通过向 Configure 函数提供 lambda 来在代码中配置强类型设置类; 例如,服务。 配置
(opt => opt.Value=true)。
Options 模式在整个 ASP.NET Core 中使用,但不是每个人都喜欢。 在下一节中,您将看到如何在没有选项模式的情况下使用强类型设置和配置绑定器。
在没有 IOptions 接口的情况下绑定强类型设置
IOptions 接口在 ASP.NET Core 中非常规范 - 它由核心 ASP.NET Core 库使用,并且具有用于绑定强类型设置的各种便利功能,正如您已经看到的。
然而,在许多情况下,IOptions 接口并没有为强类型设置对象的使用者带来很多好处。 服务必须依赖 IOptions 接口,然后通过调用 IOptions
幸运的是,将 IConfiguration 对象映射到强类型设置对象的配置绑定器本身并不与 IOptions 相关联。 清单 11.13 展示了如何手动将强类型设置对象绑定到配置部分并将其注册到 DI 容器。
清单 11.13 在 Startup.cs 中配置不带 IOptions 的强类型设置
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//创建 MapSettings 对象的新实例。
var settings = new MapSettings ();
//绑定 IConfiguration 以注册设置对象的设置。
Configuration.GetSection("MapSettings").Bind(settings);
//将设置对象注册为单例。
services.AddSingleton(settings);
}
您现在可以将 MapSettings 对象直接注入您的服务,而无需使用 IOptions
public class MyMappingController
{
private readonly MapSettings _settings;
public MyMappingController(MapSettings settings)
{
_settings = settings;
}
}
如果您使用这种方法,您将不会受益于无需进一步工作即可重新加载强类型设置的能力,或者 IOptions 的一些更高级的用法,但在大多数情况下,这不是一个大问题。 我通常是这种方法的粉丝,但与往常一样,在全心全意地采用它之前,请考虑你正在失去的东西。
这将我们带到本节关于强类型设置的结尾。 在下一节中,我们将了解如何根据应用运行的环境在运行时动态更改设置。
为多个环境配置应用程序
在本节中,您将了解 ASP.NET Core 中的托管环境。 您将学习如何设置和确定应用程序在哪个环境中运行,以及如何根据环境更改使用哪些配置值。 例如,与开发相比,这使您可以轻松地在生产中的不同配置值集之间切换。
任何投入生产的应用程序都可能必须在多个环境中运行。例如,如果您正在构建一个具有数据库访问权限的应用程序,您可能会在您的机器上运行一个用于开发的小型数据库。 在生产中,您将在其他地方的服务器上运行一个完全不同的数据库。
另一个常见的要求是根据您的应用程序运行的位置具有不同数量的日志记录。 在开发中生成大量日志非常好,因为它有助于调试,但是一旦进入生产环境,过多的日志记录可能会让人不知所措。 你会想要记录警告和错误,也许是信息级日志,但绝对不是调试级日志!
要处理这些要求,您需要确保您的应用根据其运行的环境加载不同的配置值:在生产时加载生产数据库连接字符串,等等。 你需要考虑三个方面:
- 您的应用程序如何识别它在哪个环境中运行?
- 如何根据当前环境加载不同的配置值?
- 如何改变特定机器的环境?
本节依次解决这些问题中的每一个,因此您可以轻松地将开发机器与生产服务器区分开来并采取相应的行动。
识别托管环境
正如您在第 11.2 节中看到的,HostBuilder 上的 ConfigureHostingConfiguration 方法是您定义应用程序如何计算托管环境的地方。默认情况下,CreateDefaultBuilder 使用环境变量来识别当前环境,这也许并不奇怪。 HostBuilder 寻找一个名为 ASPNETCORE_ENVIRONMENT 的神奇环境变量,并使用它来创建一个 IHostEnvironment 对象。
注意 您可以使用 DOTNET_ENVIRONMENT 或 ASPNETCORE_ENVIRONMENT 环境变量。 如果两者都设置,则 ASPNETCORE_ 值将覆盖 DOTNET_ 值。 我在本书中使用 ASPNETCORE_ 版本。
IHostEnvironment 接口公开了许多关于应用程序运行上下文的有用属性。其中一些您已经见过,例如 ContentRootPath,它指向包含应用程序内容文件的文件夹; 例如,appsettings.json 文件。 您在这里感兴趣的属性是 EnvironmentName。
IHostEnvironment.EnvironmentName 属性设置为 ASPNETCORE_ENVIRONMENT 环境变量的值,所以它可以是任何值,但在大多数情况下你应该坚持三个常用的值:
"Development"
"Staging"
"Production"
ASP.NET Core 包含几个用于处理这三个值的辅助方法,因此如果您坚持使用它们,您将拥有更轻松的时间。 特别是,当您测试您的应用程序是否在特定环境中运行时,您应该使用以下扩展方法之一:
IHostEnvironment.IsDevelopment()
IHostEnvironment.IsStaging()
IHostEnvironment.IsProduction()
IHostEnvironment.IsEnvironment(string environmentName)
这些方法都确保它们对环境变量进行不区分大小写的检查,因此如果您不将环境变量值大写,则在运行时不会出现任何异常错误。
提示 在可能的情况下,使用 IHostEnvironment 扩展方法而不是直接与 EnvironmentValue 进行字符串比较,因为它们提供不区分大小写的匹配。
IHostEnvironment 除了公开您当前环境的详细信息之外不做任何事情,但您可以通过多种方式使用它。 在第 8 章中,你看到了 Environment Tag Helper,你曾经根据当前环境显示和隐藏 HTML。现在你知道它从哪里获取信息——IHostEnvironment。
您可以使用类似的方法来自定义在运行时加载哪些配置值,方法是在开发和生产中运行时加载不同的文件。 这很常见,并且包含在大多数 ASP.NET Core 模板以及 CreateDefaultBuilder 帮助器方法中。
加载特定于环境的配置文件
EnvironmentName 值是在启动应用程序的早期确定的,在 ConfigurationBuilder 传递给 ConfigureAppConfiguration 之前
被建造。 这意味着您可以动态更改将哪些配置提供程序添加到构建器,从而在构建 IConfiguration 时加载哪些配置值。
一种常见的模式是在默认 appsettings.json 文件之后加载一个可选的、特定于环境的 appsettings.ENVIRONMENT.json 文件。 如果您在 Program.cs 中自定义 ConfigureAppConfiguration 方法,此清单显示了如何实现此目的。
清单 11.14 添加特定于环境的 appsettings.json 文件
public class Program
{
public static void AddAppConfiguration(
HostBuilderContext hostingContext,
IConfigurationBuilder config)
{
//当前的 IHostEnvironment 在 HostBuilderContext 上可用。
var env = hostingContext.HostingEnvironment;
config
.AddJsonFile(
"appsettings.json",
optional: false) //强制要求基本 appsettings.json 是很常见的。
.AddJsonFile //添加一个可选的特定于环境的 JSON 文件,其中文件名随环境而变化
$"appsettings.{env.EnvironmentName}.json",
optional: true);
}
}
使用此模式,全局 appsettings.json 文件包含适用于大多数环境的设置。 附加的可选 JSON 文件称为 appsettings.Development.json、appsettings.Staging.json 和 appsettings.Production.json 随后将添加到 ConfigurationBuilder,具体取决于当前的 EnvironmentName。
这些文件中的任何设置都将覆盖全局 appsettings.json 中的值,如果它们具有相同的键,如您之前所见。 这使您可以执行诸如仅在开发环境中将日志记录设置为详细的操作,并在生产环境中切换到更具选择性的日志。
另一种常见的模式是根据环境完全添加或删除配置提供程序。 例如,您可能在本地开发时使用 User Secrets 提供程序,但在生产中使用 Azure Key Vault。 此清单显示了如何使用 IHostEnvironment 有条件地仅在开发中包含 User Secrets 提供程序。
清单 11.15 有条件地包含 User Secrets 配置提供程序
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
HostBuilderContext hostingContext,
IConfigurationBuilder config)
{
var env = hostingContext.HostingEnvironment
config
.AddJsonFile(
"appsettings.json",
optional: false)
.AddJsonFile(
$"appsettings.{env.EnvironmentName}.json",
optional: true);
//扩展方法使检查环境变得简单而明确。
if(env.IsDevelopment())
{
//在暂存和生产中,不会使用 User Secrets 提供程序。
builder.AddUserSecrets<Startup>();
}
}
}
根据环境自定义应用程序的中间件管道也很常见。 在第 3 章中,您了解了 DeveloperExceptionPageMiddleware 以及在本地开发时应该如何使用它。 以下清单显示了如何使用 IHostEnvironment 以这种方式控制管道,以便在暂存或生产时,您的应用程序使用 ExceptionHandlerMiddleware 代替。
清单 11.16 使用宿主环境来定制你的中间件管道
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//在开发中,将 DeveloperExceptionPageMiddleware添加到管道中。
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//在登台或生产中,管道改为使用ExceptionHandlerMiddleware。
app.UseExceptionHandler("/Error");
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
注意 在清单 11.16 中,我们注入了 IWebHostEnvironment 而不是 IHostEnvironment。 此接口通过添加 WebRootPath 属性(应用程序中 wwwroot 文件夹的路径)扩展了 IHostEnvironment。 我们在这里不需要那条路径,但是知道它的存在是件好事!
您可以在应用程序的任何位置注入 IHostEnvironment,但我建议您不要在启动和程序之外的您自己的服务中使用它。 最好使用配置提供程序根据当前托管环境自定义强类型设置,然后将这些设置注入您的应用程序。
尽管它很有用,但如果您想在测试期间在不同环境之间来回切换,使用环境变量设置 IHostEnvironment 可能会有点麻烦。 就个人而言,我总是忘记如何在我使用的各种操作系统上设置环境变量。 最后要教大家的技巧是本地开发时如何设置宿主环境。
设置托管环境
在本节中,我将向您展示在开发时设置托管环境的几种方法。 这使得在不同环境中测试特定应用程序的行为变得容易,而无需更改计算机上所有应用程序的环境。
如果你的 ASP.NET Core 应用程序在启动时找不到 ASPNETCORE_ENVIRONMENT 环境变量,则默认为生产环境,如图 11.6 所示。 这意味着当您部署到生产环境时,您将默认使用正确的环境。
提示 默认情况下,当前主机环境在启动时会记录到控制台,这对于快速检查环境变量是否已正确获取很有用。
图 11.6 默认情况下,ASP.NET Core 应用程序在生产托管环境中运行。 您可以通过设置 ASPNETCORE_ENVIRONMENT 变量来覆盖它。 |
---|
另一种选择是使用 launchSettings.json 文件来控制环境。 所有默认的 ASP.NET Core 应用程序都在 Properties 文件夹中包含此文件。 LaunchSettings.json 定义运行应用程序的配置文件。
提示 配置文件可用于使用不同的环境变量运行您的应用程序。 通过使用 IIS Express 配置文件,您还可以使用配置文件来模拟在 IIS 后面的 Windows 上运行。 就个人而言,我很少使用这个配置文件,即使在 Windows 上也是如此,我总是选择“项目”配置文件。
下面的清单显示了一个典型的 launchSettings.json 文件,它定义了两个配置文件:“IISExpress”和“StoreViewerApplication”。 后一个配置文件等效于使用 dotnet run 运行项目,并且它通常与包含 launchSettings.json 文件的项目命名相同。
清单 11.17 定义两个配置文件的典型 launchSettings.json 文件
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53846",
"sslPort": 44399
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"StoreViewerApplication": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl":
"https://localhost:5001;http://localhost:5000"
}
}
}
在本地使用 launchSettings.json 文件的优点是它允许您为项目设置“local”环境变量。 例如,在清单 11.17 中,环境设置为开发环境。 这使您可以为每个项目甚至每个配置文件使用不同的环境变量,并将它们存储在源代码管理中。
您可以通过从工具栏上 Debug 按钮旁边的下拉列表中选择来选择要在 Visual Studio 中使用的配置文件,如图 11.7 所示。 您可以使用 dotnet run --launch-profile
图 11.7 您可以通过从 Debug 下拉列表中选择来从 Visual Studio 中选择要使用的配置文件。Visual Studio 默认使用 IIS Express 配置文件。 使用 dotnet run 运行的默认配置文件是第一个“项目”配置文件——在本例中为 StoreViewerApplication。 |
---|
如果您使用的是 Visual Studio,您还可以直观地编辑 launchSettings.json 文件:双击“属性”节点并选择“调试”选项卡。 您可以在图 11.8 中看到 ASPNETCORE_ENVIRONMENT 设置为开发; 在此选项卡中所做的任何更改都会反映在 launchSettings.json 中。
图 11.8 如果您愿意,可以使用 Visual Studio 编辑 launchSettings.json 文件。 更改将在 launchSettings.json 文件和“属性”对话框之间进行镜像。 |
---|
launchSettings.json 文件仅用于本地开发; 默认情况下,该文件不会部署到生产服务器。 虽然您可以在生产中部署和使用该文件,但通常不值得这么麻烦。 环境变量更合适。
我用来设置生产环境的最后一个技巧是使用命令行参数。 例如,您可以像这样将环境设置为暂存:
dotnet run --no-launch-profile --environment Staging
请注意,如果有 launchSettings.json 文件,您还必须传递 --no-launch-profile ; 否则文件中的值优先。
这将我们带到了关于配置的本章的结尾。 配置并不华丽,但它是所有应用程序的重要组成部分。 ASP.NET Core 配置提供程序模型处理范围广泛的场景,让您可以将设置和机密存储在各种位置。
简单的设置可以存储在 appsettings.json 中,在开发过程中它们很容易调整和修改,并且可以使用特定于环境的 JSON 文件覆盖它们。 同时,您的机密和敏感设置可以存储在用户机密管理器中的项目文件之外,或者作为环境变量存储。 这为您提供了灵活性和安全性——只要您不将秘密写入 appsettings.json!
在下一章中,我们将简要介绍与 ASP.NET Core 完美匹配的新对象关系映射器:Entity Framework Core。 我们只会在本书中体验它,但您将学习如何加载和保存数据、从代码构建数据库以及随着代码的发展迁移数据库。
总结
- 任何可以被视为设置或机密的内容通常都存储为配置值。
- ASP.NET Core 使用配置提供程序从各种来源加载键值对。 应用程序可以使用许多不同的配置提供程序。
- ConfigurationBuilder 描述了如何为你的应用构建最终的配置表示,而 IConfiguration 自己持有配置值。
- 您可以通过使用 AddJsonFile() 等扩展方法将配置提供程序添加到 ConfigurationBuilder 的实例来创建配置对象。HostBuilder 会为您创建 ConfigurationBuilder 实例并调用 Build() 来创建 IConfiguration 的实例。
- ASP.NET Core 包括 JSON 文件、XML 文件、环境文件和命令行参数等的内置提供程序。 NuGet 包可用于许多其他提供程序,例如 YAML 文件和 Azure Key Vault。
- 将提供程序添加到 ConfigurationBuilder 的顺序很重要;后续提供程序会替换早期提供程序中定义的设置值。
- 配置键不区分大小写。
- 您可以使用索引器语法直接从 IConfiguration 检索设置;例如,Configuration["MySettings:Value"]。
- CreateDefaultBuilder 方法为您配置 JSON、环境变量、命令行参数和用户密钥提供程序。 您可以通过调用 ConfigureAppConfiguration 自定义应用程序中使用的配置提供程序。
- 在生产环境中,将机密存储在环境变量中。 这些可以在配置生成器中基于文件的设置之后加载。
- 在开发机器上,User Secrets Manager 是比使用环境变量更方便的工具。 它将机密存储在您的操作系统用户的配置文件中,位于项目文件夹之外。
- 请注意,环境变量和 User Secrets Manager 工具都不会加密机密,它们只是将它们存储在不太可能公开的位置,因为它们位于您的项目文件夹之外。
- 基于文件的提供程序,例如 JSON 提供程序,可以在文件更改时自动重新加载配置值。 这允许您实时更新配置值,而无需重新启动您的应用程序。
- 使用强类型 POCO 选项类来访问应用程序中的配置。
- 使用 ConfigureServices 中的 Configure
() 扩展方法将 POCO 选项对象绑定到 ConfigurationSection。 - 您可以使用 DI 将 IOptions
接口注入到您的服务中。 您可以访问 Value 属性上的强类型选项对象。 - 您可以通过将 lambda 传递给 Configure() 方法,在代码中配置 IOptions
对象,而不是使用配置值。 - 如果您想在配置更改时重新加载 POCO 选项对象,请改用 IOptionsSnapshot
接口。 - 在不同环境中运行的应用程序,例如开发与生产,通常需要不同的配置值。
- ASP.NET Core 使用 ASPNETCORE_ENVIRONMENT 环境变量确定当前托管环境。 如果未设置此变量,则假定环境为生产环境。
- 您可以使用 launchSettings.json 文件在本地设置托管环境。 这允许您将环境变量范围限定为特定项目。
- 您可以使用 IHostEnvironment 对象来加载特定于当前环境的文件,例如 appsettings.Production.json。