.NET应用设置揭秘(C#和VB)

目录

介绍

这是如何工作的?

如何在运行时合并设置

配置开发和生产环境

设置配置设置

文件:“appsettings.json”

文件:'appsettings.Development.json'

文件:'appsettings.Production.json'

实现

设置

示例控制台应用程序(依赖项注入)

1. 设置依赖注入

2. 检索强类型选项

3. 检索单个值

示例控制台应用程序(无依赖项注入)

如何测试

结论

引用


介绍

ASP.NET 应用程序通过环境驱动的多个文件(appsettings.jsonappsettings.Development.jsonappsettings.Production.json文件)支持开箱即用的appsettings.json开发和生产设置

appsettings.json只是可以设置应用程序设置的众多位置之一。您可以在此处阅读有关此内容的详细信息:.NET中的配置提供程序 |Microsoft 学习

本文将重点介绍如何向其他应用程序类型添加对appsettings.json的支持,特别是Dot Net Core ConsoleWinformsWPF类型。

虽然不重要,但为了更好地理解,我们将研究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 用于DebugRelease两者模式,即使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,日志为WarningErrorCritical的日志记录。

这同样适用于 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包:

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

如何测试

我们将逐步配置设置,以了解它们在DebugRelease两种模式下的工作方式。

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文件,将相同的行为添加到我们自己的控制台WinformsWPF应用。

引用

https://www.codeproject.com/Articles/5354478/NET-App-Settings-Demystified-Csharp-VB

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值