目录
文件:'appsettings.Development.json'
文件:'appsettings.Production.json'
- 下载AppSettings_v1.20 - 181.2 KB [新增]
- 下载AppSettings_v1.11 - 178 KB [已过时]
- 下载AppSettings_v1.01 - 166.6 KB [已过时]
介绍
ASP.NET 应用程序通过环境驱动的多个文件(appsettings.json、appsettings.Development.json和appsettings.Production.json文件)支持开箱即用的appsettings.json开发和生产设置。
appsettings.json只是可以设置应用程序设置的众多位置之一。您可以在此处阅读有关此内容的详细信息:.NET中的配置提供程序 |Microsoft 学习
本文将重点介绍如何向其他应用程序类型添加对appsettings.json的支持,特别是Dot Net Core Console、Winforms和WPF类型。
虽然不重要,但为了更好地理解,我们将研究Dot Net Core框架源代码,看看Microsoft的配置是如何工作的。我们将介绍的内容记录在上面的链接中。然后,我们将在Console应用程序中进行快速测试,以更好地了解如何以及为什么。
注意: 这纯粹是一篇专注于Dot Net Core的文章,而不是.Net Framework。
这是如何工作的?
我们需要看看框架如何连接appsettings.json。为此,我们需要探索框架代码中的实现,特别是HostingHostBuilderExtensions类中的Hostbuilder.ConfigureDefaults:
internal static void ApplyDefaultHostConfiguration
(IConfigurationBuilder hostConfigBuilder, string[]? args)
{
/*
If we're running anywhere other than C:\Windows\system32,
we default to using the CWD for the ContentRoot.
However, since many things like Windows services and MSIX installers have
C:\Windows\system32 as there CWD which is not likely to really be
the home for things like appsettings.json, we skip
changing the ContentRoot in that case. The non-"default" initial
value for ContentRoot is AppContext.BaseDirectory
(e.g. the executable path) which probably
makes more sense than the system32.
In my testing, both Environment.CurrentDirectory and Environment.GetFolderPath(
Environment.SpecialFolder.System) return the path without
any trailing directory separator characters. I'm not even sure
the casing can ever be different from these APIs, but I think
it makes sense to ignore case for Windows path comparisons
given the file system is usually
(always?) going to be case insensitive for the system path.
*/
string cwd = Environment.CurrentDirectory;
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
!string.Equals(cwd, Environment.GetFolderPath
(Environment.SpecialFolder.System),
StringComparison.OrdinalIgnoreCase))
{
hostConfigBuilder.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, cwd),
});
}
hostConfigBuilder.AddEnvironmentVariables(prefix: "DOTNET_");
if (args is { Length: > 0 })
{
hostConfigBuilder.AddCommandLine(args);
}
}
在这里,我们可以看到使用了带有DOTNET_前缀的Environment变量。
可以在此处阅读有关此内容的详细信息:.NET泛型主机 |Microsoft学习。
为了获得后缀,我们查看IHostEnvironment中该EnvironmentName属性的注释:
internal static void ApplyDefaultHostConfiguration
(IConfigurationBuilder hostConfigBuilder, string[]? args)
{
/*
If we're running anywhere other than C:\Windows\system32,
we default to using the CWD for the ContentRoot.
However, since many things like Windows services and MSIX installers have
C:\Windows\system32 as there CWD which is not likely to really be
the home for things like appsettings.json, we skip
changing the ContentRoot in that case. The non-"default" initial
value for ContentRoot is AppContext.BaseDirectory
(e.g. the executable path) which probably
makes more sense than the system32.
In my testing, both Environment.CurrentDirectory and Environment.GetFolderPath(
Environment.SpecialFolder.System) return the path without
any trailing directory separator characters. I'm not even sure
the casing can ever be different from these APIs, but I think
it makes sense to ignore case for Windows path comparisons
given the file system is usually
(always?) going to be case insensitive for the system path.
*/
string cwd = Environment.CurrentDirectory;
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
!string.Equals(cwd, Environment.GetFolderPath
(Environment.SpecialFolder.System),
StringComparison.OrdinalIgnoreCase))
{
hostConfigBuilder.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, cwd),
});
}
hostConfigBuilder.AddEnvironmentVariables(prefix: "DOTNET_");
if (args is { Length: > 0 })
{
hostConfigBuilder.AddCommandLine(args);
}
}
所以后缀是Environment。所以完整的环境变量名是DOTNET_ENVIRONMENT。与ASP.NET及其ASPNETCORE_ENVIRONMENT变量不同,默认情况下不设置DOTNET_ENVIRONMENT。
如果未设置环境变量,则EnviromentName默认为 Production,并且如果它存在,appsettings.Production.json 用于Debug和Release两者模式,即使appsettings.Development.json存在。Appsettings.Development.json 将被忽略。
如何在运行时合并设置
我们需要看一下HostingHostBuilderExtensions类中的另一个扩展方法:
internal static void ApplyDefaultAppConfiguration(
HostBuilderContext hostingContext,
IConfigurationBuilder appConfigBuilder, string[]? args)
{
IHostEnvironment env = hostingContext.HostingEnvironment;
bool reloadOnChange = GetReloadConfigOnChangeValue(hostingContext);
appConfigBuilder
.AddJsonFile("appsettings.json", optional: true,
reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: reloadOnChange);
if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly is not null)
{
appConfigBuilder.AddUserSecrets(appAssembly, optional: true,
reloadOnChange: reloadOnChange);
}
}
appConfigBuilder.AddEnvironmentVariables();
if (args is { Length: > 0 })
{
appConfigBuilder.AddCommandLine(args);
}
我们感兴趣的是:
appConfigBuilder.AddJsonFile("appsettings.json", optional: true,
reloadOnChange: reloadOnChange) .AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChange: reloadOnChange);
在这里,我们可以看到首先加载了appsettings.json,然后加载了 $“appsettings.{env.EnvironmentName}.json“,其中EnvironmentName依赖于DOTNET_ENVIRONMENT环境变量。正如我们从上面知道的,除非我们手动选择一个,否则EnvironmentName将默认为Production。
appsettings.json中的任何设置都将被appsettings.Production.json值覆盖。(如果已设置)。
配置开发和生产环境
若要设置DOTNET_ENVIRONMENT变量,请在“解决方案资源管理器”中右键单击应用程序名称,打开应用程序“属性”,导航到“调试>常规”部分,然后单击“打开调试启动配置文件UI”。这将打开“启动配置文件”窗口。
或者,我们可以从工具栏访问“启动配置文件”窗口:
我们感兴趣的是Environment variables。您可以在此处设置任何内容。我们需要添加的是名称:DOTNET_ENVIRONMENT,值为:Development。没有关闭按钮,只需关闭窗口,然后从VS菜单中选择“文件”>“保存...”。
这样做的目的是将新文件夹 Properties 添加到解决方案文件夹的根目录,并使用以下内容创建 launchSettings.json 文件:
{
"profiles": {
"[profile_name_goes_here]": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "DEVELOPMENT"
}
}
}
}
注意:launchSettings.json文件仅适用于Visual Studio。它不会被复制到已编译的文件夹中。如果将它包含在应用程序中,运行时将忽略它。需要在应用程序中手动支持此文件,以便在Visual Studio外部使用。
设置配置设置
我们希望开发和生产环境设置不同的设置,因此我们配置文件:
- appsettings.json——保存两个环境的根配置
- appsettings.Development.json——开发期间使用的设置
- appsettings.Production.json——用于已部署的实时应用程序的设置
注意
- 所有appsettings文件都需要标记为“内容”和“复制”(如果较新)才能在调试和发布模式下使用
- 由于所有 appsettings 文件都将位于Release文件夹中,因此需要将安装程序配置为仅打包所需的文件——不包括 appsettings.Development.json文件。
您可以设置多个启动配置文件。上面,我设置了第三个(3),每个都有自己的环境变量设置:
- 开发——Development
- 测试——Staging
- 生产——无(默认为Production)
要选择要测试的配置文件,我们可以从工具栏中进行选择:
文件:“appsettings.json”
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
文件:'appsettings.Development.json'
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
}
}
文件:'appsettings.Production.json'
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
}
}
如果只有appsettings.json,则日志记录将至少是Information级别的。
如果处于调试模式,则为 appsettings.Development.json,并启用所有日志记录。
如果处于发布模式,则为appsettings.Production.json,日志为Warning、Error和Critical的日志记录。
这同样适用于 appsettings.json文件中设置的其他应用程序选项。
实现
我们将创建一个Console应用程序来检查上面学到的内容。我们将使用示例选项appsettings并将它们映射到选项类。代码是最小的,以保持实现易于理解。
设置
我们需要准备选项。我们将需要一个类来映射appsettings文件中的选项部分设置:
1、我们的设置选项类:
public class MySectionOptions
{
public string? Setting1 { get; set; }
public int? Setting2 { get; set; }
}
Public Class MySectionOptions
Property Setting1 As String
Property Setting2 As Integer
End Class
2、添加到appsettings文件:
现在为不同的配置设置选项。
1、appsettings.json文件:
{
"MySection": {
"setting1": "default_value_1",
"setting2": 222
}
}
2、appsettings.Development.json文件:
{
"MySection": {
"setting1": "development_value_1",
"setting2": 111
}
}
3、appsettings.Production.json文件:
{
"MySection": {
"setting1": "production_value_1",
"setting2": 333
}
}
注意:
*我们设置了对配置的强类型访问,以便使用选项模式。有关详细信息,请阅读本文:.Net中的选项模式 |Microsoft 学习
示例控制台应用程序(依赖项注入)
现在我们可以从appsettings中读取选项:
1. 设置依赖注入
IHostBuilder builder = Host.CreateDefaultBuilder();
// Map the options class to the section in the `appsettings`
builder.ConfigureServices((context, services) =>
{
IConfiguration configRoot = context.Configuration;
services.Configure<MySectionOptions>
(configRoot.GetSection("MySection"));
});
IHost host = builder.Build();
Dim builder As IHostBuilder = Host.CreateDefaultBuilder()
' Map the options class to the section in the `appsettings`
builder.ConfigureServices(
Sub(context, services)
Dim configRoot As IConfiguration = context.Configuration
services.Configure(Of MySectionOptions)(configRoot.GetSection("MySection"))
End Sub)
Dim _host As IHost = builder.Build()
注意:我们已经定义,我们将检索使用选项模式的强类型选项部分。
2. 检索强类型选项
// If environment variable not set, will default to "Production"
string env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";
Console.WriteLine($"Environment: {env}");
// Get the options from `appsettings`
MySectionOptions options = host.Services
.GetRequiredService<IOptions<MySectionOptions>>()
.Value;
Console.WriteLine($"Setting1: {options.Setting1}");
Console.WriteLine($"Setting2: {options.Setting2}");
Console.ReadKey();
' If environment variable not set, will default to "Production"
Dim env As String = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
If String.IsNullOrWhiteSpace(env) Then
env = "Production"
End If
Console.WriteLine($"Environment: {env}")
' Get the options from `appsettings`
Dim options As MySectionOptions = _host.Services _
.GetRequiredService(Of IOptions(Of MySectionOptions)) _
.Value
Console.WriteLine($"Setting1: {options.Setting1}")
Console.WriteLine($"Setting2: {options.Setting2}")
Console.ReadKey()
3. 检索单个值
在上面,我们研究了如何检索强类型部分。如果我们只对单个键值对感兴趣,该怎么办?我们也可以这样做:
// manually retrieve values
IConfiguration config = host.Services.GetRequiredService<IConfiguration>();
// Log Level section
Console.WriteLine("By Individual LogLevel key [Logging:LogLevel:Default]");
IConfigurationSection logLevel = config.GetSection("Logging:LogLevel:Default");
Console.WriteLine($" LogLevel: {logLevel.Value}");
Console.WriteLine();
// Option Settings section
Console.WriteLine("By Individual Option keys");
IConfigurationSection setting1 = config.GetSection("MySection:setting1");
Console.WriteLine($" Setting1: {setting1.Value}");
IConfigurationSection setting2 = config.GetSection("MySection:setting2");
Console.WriteLine($" Setting2: {setting2.Value}");
' manually retrieve values
dim config As IConfiguration = _host.Services.GetRequiredService(of IConfiguration)
' Log Level section
Console.WriteLine("By Individual LogLevel key [Logging:LogLevel:Default]")
dim logLevel = config.GetSection("Logging:LogLevel:Default")
Console.WriteLine($" LogLevel: {logLevel.Value}")
Console.WriteLine()
' Option Settings section
Console.WriteLine("By Individual Option keys")
dim setting1 As IConfigurationSection = config.GetSection("MySection:setting1")
Console.WriteLine($" Setting1: {setting1.Value}")
dim setting2 as IConfigurationSection = config.GetSection("MySection:setting2")
Console.WriteLine($" Setting2: {setting2.Value}")
注意:要读取单个键值对,我们获取对DI选项配置的引用,然后检索信息。
示例控制台应用程序(无依赖项注入)
不是每个人都使用依赖注入,所以我创建了一个帮助程序类来抽象出读取appsettings.json配置信息所需的代码:
需要以下NuGet包:
- Microsoft.Extensions.Configuration
- Microsoft.Extensions.Configuration.EnviraonmentVariables
- Microsoft.Extensions.Configuration.Json
- Microsoft.Extensions.Options.ConfigurationExtensions
public class AppSettings<TOption>
{
#region Constructors
public AppSettings(IConfigurationSection configSection, string? key = null)
{
_configSection = configSection;
GetValue(key);
}
#endregion
#region Fields
protected static AppSettings<TOption>? _appSetting;
protected static IConfigurationSection? _configSection;
#endregion
#region Properties
public TOption? Value { get; set; }
#endregion
#region Methods
public static TOption? Current(string section, string? key = null)
{
_appSetting = GetCurrentSettings(section, key);
return _appSetting.Value;
}
public static AppSettings<TOption>
GetCurrentSettings(string section, string? key = null)
{
string env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ??
"Production";
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
IConfigurationRoot configuration = builder.Build();
if (string.IsNullOrEmpty(section))
section = "AppSettings"; // default
AppSettings<TOption> settings =
new AppSettings<TOption>(configuration.GetSection(section), key);
return settings;
}
protected virtual void GetValue(string? key)
{
if (key is null)
{
// no key, so must be a class/strut object
Value = Activator.CreateInstance<TOption>();
_configSection.Bind(Value);
return;
}
Type optionType = typeof(TOption);
if ((optionType == typeof(string) ||
optionType == typeof(int) ||
optionType == typeof(long) ||
optionType == typeof(decimal) ||
optionType == typeof(float) ||
optionType == typeof(double))
&& _configSection != null)
{
// we must be retrieving a value
Value = _configSection.GetValue<TOption>(key);
return;
}
// Could not find a supported type
throw new InvalidCastException($"Type {typeof(TOption).Name} is invalid");
}
#endregion
}
Public Class AppSettings(Of TOption)
#Region "Constructors"
Public Sub New(configSection As IConfigurationSection, _
Optional key As String = Nothing)
_configSection = configSection
GetValue(key)
End Sub
#End Region
#Region "Fields"
Protected Shared _appSetting As AppSettings(Of TOption)
Protected Shared _configSection As IConfigurationSection
#End Region
#Region "Properties"
Public Property Value As TOption
#End Region
#Region "Methods"
Public Shared Function Current(section As String, _
Optional key As String = Nothing) As TOption
_appSetting = GetCurrentSettings(section, key)
Return _appSetting.Value
End Function
Public Shared Function GetCurrentSettings(section As String, _
Optional key As String = Nothing) As AppSettings(Of TOption)
Dim env As String = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
If String.IsNullOrWhiteSpace(env) Then
env = "Production"
End If
Dim builder As IConfigurationBuilder = New ConfigurationBuilder() _
.SetBasePath(Directory.GetCurrentDirectory()) _
.AddJsonFile("appsettings.json", optional:=True, reloadOnChange:=True) _
.AddJsonFile($"appsettings.{env}.json", _
optional:=True, reloadOnChange:=True) _
.AddEnvironmentVariables()
Dim configuration As IConfigurationRoot = builder.Build()
If String.IsNullOrEmpty(section) Then
section = "AppSettings" ' Default
End If
Dim settings As AppSettings(Of TOption) = _
New AppSettings(Of TOption)(configuration.GetSection(section), key)
Return settings
End Function
Protected Overridable Sub GetValue(Optional key As String = Nothing)
If key Is Nothing Then
' no key, so must be a class/strut object
Value = Activator.CreateInstance(Of TOption)
_configSection.Bind(Value)
Return
End If
Dim optionType As Type = GetType(TOption)
If (optionType Is GetType(String) OrElse
optionType Is GetType(Integer) OrElse
optionType Is GetType(Long) OrElse
optionType Is GetType(Decimal) OrElse
optionType Is GetType(Single) OrElse
optionType Is GetType(Double)) _
AndAlso _configSection IsNot Nothing Then
' we must be retrieving a value
Value = _configSection.GetValue(Of TOption)(key)
Return
End If
' Could not find a supported type
Throw New InvalidCastException($"Type {GetType(TOption).Name} is invalid")
End Sub
#End Region
End Class
注意
- AppSettings帮助程序类适用于Option类和单个键值。
使用帮助程序类非常简单:
Console.WriteLine("By Individual LogLevel key [Logging:LogLevel:Default]");
string? logLevel = AppSettings<string>.Current("Logging:LogLevel", "Default");
Console.WriteLine($" LogLevel: {logLevel}");
Console.WriteLine();
Console.WriteLine("By Individual keys");
string? setting1 = AppSettings<string>.Current("MySection", "Setting1");
int? setting2 = AppSettings<int>.Current("MySection", "Setting2");
Console.WriteLine($" Setting1: {setting1}");
Console.WriteLine($" Setting2: {setting2}");
Console.WriteLine();
Console.WriteLine("By Option Class");
MySectionOptions? options = AppSettings<MySectionOptions>.Current("MySection");
Console.WriteLine($" Setting1: {options?.Setting1}");
Console.WriteLine($" Setting2: {options?.Setting2}");
Console.ReadKey();
Console.WriteLine("By Individual LogLevel key [Logging:LogLevel:Default]")
dim logLevel = AppSettings(Of String).Current("Logging:LogLevel", "Default")
Console.WriteLine($" LogLevel: {logLevel}")
Console.WriteLine()
Console.WriteLine("By Individual keys")
Dim setting1 = AppSettings(Of String).Current("MySection", "Setting1")
Dim setting2 = AppSettings(Of Integer).Current("MySection", "Setting2")
Console.WriteLine($" Setting1: {setting1}")
Console.WriteLine($" Setting2: {setting2}")
Console.WriteLine()
Console.WriteLine("By Option Class")
Dim options = AppSettings(Of MySectionOptions).Current("MySection")
Console.WriteLine($" Setting1: {options.Setting1}")
Console.WriteLine($" Setting2: {options.Setting2}")
Console.ReadKey()
如何测试
我们将逐步配置设置,以了解它们在Debug和Release两种模式下的工作方式。
1、仅使用 appsettings.json 文件进行测试:
Environment: Production
By Individual LogLevel key [Logging:LogLevel:Default]
LogLevel: Information
By Individual Option keys
Setting1: default_value_1
Setting2: 222
By Option Class
Setting1: default_value_1
Setting2: 222
2、现在包括 appsettings.Production.json 文件:
Environment: Production
By Individual LogLevel key [Logging:LogLevel:Default]
LogLevel: Warning
By Individual Option keys
Setting1: production_value_1
Setting2: 333
By Option Class
Setting1: production_value_1
Setting2: 333
3、现在包括 appsettings.Develpment.json 文件:
Environment: Production
By Individual LogLevel key [Logging:LogLevel:Default]
LogLevel: Warning
By Individual Option keys
Setting1: production_value_1
Setting2: 333
By Option Class
Setting1: production_value_1
Setting2: 333
4、设置launchSetting.json文件:
Environment: Development
By Individual LogLevel key [Logging:LogLevel:Default]
LogLevel: Trace
By Individual Option keys
Setting1: development_value_1
Setting2: 111
By Option Class
Setting1: development_value_1
Setting2: 111
但是等等,对于测试4。在Release模式下,并且在没有调试的情况下启动,我们仍然看到Environment: Development。这是因为launchSetting.json环境变量。
我们需要进入命令行,在bin\Release\net7.0文件夹中运行应用程序,然后我们将看到以下输出:
Environment: Production
By Individual LogLevel key [Logging:LogLevel:Default]
LogLevel: Warning
By Individual Option keys
Setting1: production_value_1
Setting2: 333
By Option Class
Setting1: production_value_1
Setting2: 333
或者我们可以设置另一个启动配置文件来模拟生产/发布模式——请参阅开发和生产环境的配置(上面章节)。
结论
虽然 ASP.NET Core开箱即用,但我们可以通过在launchsettings.json中添加我们自己的环境变量,然后设置appsettings文件,将相同的行为添加到我们自己的控制台、Winforms或WPF应用。
引用
- .NET中的选项模式
- .NET中的配置提供程序 | Microsoft学习
- .NET泛型主机 | Microsoft学习
- HostingHostBuilderExtensions.cs | Github - dotnet/dotnet
- 文章图片提供:如何在夜间拍摄火箭发射
https://www.codeproject.com/Articles/5354478/NET-App-Settings-Demystified-Csharp-VB