官术网_书友最值得收藏!

Providers

The available Microsoft configuration providers (and their NuGet packages) are as follows:

  • JavaScript Object Notation (JSON) files: Microsoft.Extensions.Configuration.Json
  • XML files: Microsoft.Extensions.Configuration.Xml
  • Initialization (INI) files: Microsoft.Extensions.Configuration.Ini
  • User secrets: Microsoft.Extensions.Configuration.UserSecrets
  • Azure Key Vault: Microsoft.Extensions.Configuration.AzureKeyVault
  • Environment variables: Microsoft.Extensions.Configuration.EnvironmentVariables
  • Command line: Microsoft.Extensions.Configuration.CommandLine
  • Memory: Microsoft.Extensions.Configuration
  • Docker secrets: Microsoft.Extensions.Configuration.DockerSecrets
Some of these are based upon the FileConfigurationProviderclass: JSON, XML, and INI.

When you reference these packages, you automatically make their extensions available. So, for example, if you want to add the JSON provider, you have two options, detailed next.

You can add a JsonConfigurationSource directly, like this:

var jsonSource = new JsonConfigurationSource { 
Path = "appsettings.json" }; builder.Add(jsonSource);

Alternatively, you can use the AddJsonFile extension method, like this:

builder.AddJsonFile("appsettings.json");

Most likely, the extension methods are what you need. As I said, you can have any number of providers at the same time, as illustrated in the following code snippet:

builder
    .AddJsonFile("appsettings.json")
    .AddEnvironmentVariables()
    .AddXmlFile("web.config");

You just need to keep in mind that if two providers return the same configuration setting, the order by which they were added matters; the result you get will come from the last provider added, as it will override the previous ones. So, for example, imagine you are adding two JSON configuration files, one that is common across all environments (development, staging, and production), and another for a specific environment; in this case, you would likely have the following:

builder
    .AddJsonFile("appsettings.json")
    .AddJsonFile($"appsettings.{env.EnvironmentName}.json");
This is so the environment-specific configuration file takes precedence.

Each provider will, of course, feature different properties for setting up; all file-based providers will require, for instance, a file path, but that doesn't make sense when we're talking about environment variables.

File-based providers

Both JSON, XML, and INI configuration sources are based on files. Therefore, their classes inherit from the FileConfigurationSource abstract base class. This class offers the following configuration properties:

  • Path: The actual, fully qualified physical path where the file is to be found; this is a required setting.
  • Optional: A Boolean flag for specifying whether the absence of the file causes a runtime error (false) or not (true); the default is false.
  • ReloadOnChange: Here, you decide whether to automatically detect changes to the source file (true) or not (false); the default is false.
  • ReloadDelay: The delay, in milliseconds, before reloading the file in the event that a change was detected (ReloadOnChange set to true); the default is 250 milliseconds.
  • OnLoadException: A delegate to be called should an error occur while parsing the source file; this is empty by default.
  • FileProvider: The file provider that actually retrieves the file; the default is an instance of PhysicalFileProvider, set with the folder of the Path property.

All of the extension methods allow you to supply values for each of these properties, except OnLoadException. You are also free to specify your own concrete implementation of IFileProvider, which you should do if you have specific needs, such as getting files from inside a ZIP file. ConfigurationBuilder has an extension method, SetBasePath, that sets a default PhysicalFileProvider pointing to a folder on your filesystem so that you can pass relative file paths to the configuration source's Path property.

If you set ReloadOnChange to true, .NET Core will start an operating system-specific file that monitors a watch on the source file; because these things come with a cost, try not to have many watches.

A typical example would be as follows:

builder
    .SetBasePath(@"C:\Configuration")
    .AddJsonFile(path: "appsettings.json", optional: false, 
reloadOnChange: true) .AddJsonFile(path: $"appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChange: true);

This would result in the appsettings.json file being loaded from the C:\Configuration folder (and throwing an exception if it is not present), and then loading appsettings.Development.json (this time, ignoring it if the file doesn't exist). Whenever there's a change in either file, they are reloaded and the configuration is updated.

Very important: in operating systems or filesystems where the case matters, such as Linux, make sure that the name of the file that takes the environment name (for example, appsettings.Development.json) is in the right case—otherwise, it won't be found!

If, however, we wanted to add an error handler, we need to add the configuration source manually, as follows:

var jsonSource = new JsonConfigurationSource { Path = "filename.json" };
jsonSource.OnLoadException = (x) =>
{
    if (x.Exception is FileNotFoundException ex)
    {
        Console.Out.WriteLine($"File {ex.FileName} not found");
        x.Ignore = true;
    }
};
builder.Add(jsonSource);

This way, we can prevent certain errors from crashing our application.

All file-based providers are added by an extension method with the name AddxxxFile, where xxx is the actual type—Json, Xml, or Ini—and always takes the same parameters (path, optional, and reloadOnChange).

JSON provider

We typically add a JSON configuration file using the AddJsonFile extension method. The JSON provider will load a file containing JSON contents and make its structure available for configuration, using dotted notation. A typical example is shown in the following code snippet:

{
  "ConnectionStrings": {
      "DefaultConnection": "Server=(localdb)mssqllocaldb; 
Database=aspnetcore" } }

Any valid JSON content will work. As of now, it is not possible to specify a schema. Sections are just sub-elements of the JSON content.

An example of code used to load a configuration value would be as follows:

var defaultConnection = cfg["ConnectionStrings:DefaultConnection"];
XML provider

XML is becoming less and less common, with JSON, inversely, becoming increasingly popular; however, there are still good reasons to use XML. So, we add an XML file using the AddXmlFile extension method, and as far as configuration is concerned, we need to wrap our XML contents in a settings node; the XML declaration is optional. Refer to the following example:

<settings Flag="2">
    <MySettings>
        <Option>10</Option>
    </MySettings>
</settings>

Again, as of now, it is not possible to specify a validating schema. With this provider, sections are implemented as sub-elements.

Two examples of this are as follows:

var flag = cfg["Flag"];
var option = cfg["MySettings:Option"];
INI provider

INI files are a thing of the past, but, for historical reasons, Microsoft is still supporting them (actually, Linux also makes use of INI files too). In case you're not familiar with its syntax, this is what it looks like:

[SectionA]
Option1=Value1
Option2=Value2

[SectionB]
Option1=Value3

You add INI files to the configuration through the AddIniFile extension method.

One word of advice: both XML and JSON file formats support anything that INI files do, so unless you have a very specific requirement, you're better off with either JSON or XML.

Sections in INI files just map to the intrinsic sections provided by the INI file specification.

A single example is as follows:

var optionB2 = cfg["SectionB:Option1"];

Other providers

Besides file-based providers, there are other ways to store and retrieve configuration information. Here, we list the currently available options in .NET Core.

User secrets

.NET Core introduced user secrets as a means of storing sensitive information per user. The benefit of this is that it is kept in a secure manner, out of configuration files, and is not visible by other users. A user secrets store is identified (for a given user) by userSecretsId, which the Visual Studio template initializes as a mix of a string and a globally unique identifier (GUID), such as aspnet-Web-f22b64ea-be5e-432d-abc6-0275a9c00377.

Secrets in a store can be listed, added, or removed through the dotnet executable, as illustrated in the following code snippet:

dotnet user-secrets list                 --lists all the values in the 
store dotnet user-secrets set "key" "value" --set "key" to be "value" dotnet user-secrets remove "key" --remove entry for "key" dotnet user-secrets clear --remove all entries

You will need the Microsoft.Extensions.SecretManager.Tools package. The dotnet user-secrets command will only work when in the presence of a project file that specifies the userSecretsId store ID. The AddUserSecrets extension method is what we use to add user secrets to the configuration, and it will either pick up this userSecretsIdsetting automatically, or you can provide your own at runtime, as follows:

builder.AddUserSecrets(userSecretdId: "[User Secrets Id]");

Another option is to get the user secrets ID from an assembly, in which case this needs to be decorated with the UserSecretsIdAttribute attribute, as follows:

[assembly: UserSecretsId("aspnet-Web-f22b64ea-be5e-432d-abc6-0275a9c00377")

In this case, the way to load it is demonstrated in the following code snippet:

builder.AddUserSecrets<Startup>();
Be warned: if you have more than one assembly with the same user secret ID (by mistake), the application will throw an exception when loading them.

Yet another way to specify user secrets (in ASP.NET Core 2.x) is through the .csproj file, by using a UserSecretsId element, as illustrated in the following code snippet:

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<UserSecretsId>9094c8e7-0000-0000-0000-c26798dc18d2</UserSecretsId>
</PropertyGroup>

Regardless of how you specify the user secrets ID, as with all the other providers, the way to load a value is as follows:

var value = cfg["key"];

In case you are interested, you can read more about .NET Core user secrets here: https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets

Azure Key Vault

Azure Key Vault is an Azure service that you can leverage for enterprise-level secure key-value storage. The full description is outside the scope of this book, but you can read about it here: https://azure.microsoft.com/en-us/services/key-vault. Suffice to say that you add the Azure Key Vault provider through the AddAzureKeyVault extension method, as depicted in this line of code:

builder.AddAzureKeyVault(vault: "https://[Vault].vault.azure.net/",
clientId: "[Client ID]", clientSecret: "[Client Secret]");

After this, all are added to the configuration object, and you can retrieve them in the usual way.

Command line

Another very popular way to get configuration settings is the command line. Executables regularly expect information to be passed in the command line, so as to dictate what should be done or to control how it should happen.

The extension method to use is AddCommandLine, and it expects a required and an optional parameter, as follows:

builder.AddCommandLine(args: Environment.GetCommandLineArgs().Skip(1).ToArray());

The args parameter will typically come from Environment.GetCommandLineArgs(), and we take the first parameter out, as this is the entry assembly's name. If we are building our configuration object in Program.Main, we can use its args parameter too.

Now, there are several ways to specify parameters. One way is illustrated in the following code snippet:

    Key1=Value1
    --Key2=Value2
    /Key3=Value3
    --Key4 Value4
    /Key5 Value5

Here is another example:

dotnet run MyProject Key1=Value1 --Key2=Value2 /Key3=Value3 --Key4 Value4 /Key5 Value5

If the value has spaces in it, you need to wrap it in quotes ("). You can't use - (single dash), as this would be interpreted as a parameter to dotnet instead.

The optional parameter to AddCommandLine, switchMappings, is a dictionary that can be used to create new keys that will duplicate those from the command line, as follows:

var switchMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
     { { "--Key1", "AnotherKey" } };

builder.AddCommandLine(
args: Environment.GetCommandLineArgs().Skip(1).ToArray(),
switchMappings: switchMappings);

These keys can even have special characters in them—for example, --a:key and /some.key are valid keys.

Again, use the same syntax to retrieve their values.

Environment variables

Environment variables exist in all operating systems and can also be regarded as a source of configuration. Many tools out there, such as Docker, rely on environment variables for getting their operating context.

Adding environment variables to a .NET Core configuration is straightforward; you just need to call AddEnvironmentVariables. By default, this will bring all the existing environment variables into the configuration, but we can also specify a prefix, and filter out all variables that do not start with it, as follows:

builder.AddEnvironmentVariables(prefix: "ASPNET_");

So, this will add both ASPNET_TargetHost and ASPNET_TargetPort, but not PATH or COMPUTERNAME.

Sections are supported if you separate names with double underscores (for example, __). For example, say you have this environment variable:

ASPNETCORE__ADMINGROUP__USERS=rjperes,pm

You could access the ADMINGROUP section like this:

var group = cfg
.GetSection("ASPNETCORE")
.GetSection("ADMINGROUP");
var users = group["USERS"];
Memory

The memory provider is a convenient way of specifying values dynamically at runtime and for using dictionary objects. We add the provider with the AddInMemoryCollection extension method, as follows:

var properties = new Dictionary<string, string> { { "key", "value" } };
builder.AddInMemoryCollection(properties);

The advantage of this approach is that it is easy to populate a dictionary with whatever values we want, particularly in unit tests.

Docker

The ability to have secrets coming from Docker-stored files is relatively new in .NET Core. Basically, it will try to load text files in a specific directory inside a Docker instance as the values where the key is the filename itself. This is an actual feature of Docker, about which you can read more here: https://docs.docker.com/engine/swarm/secrets

The AddDockerSecrets extension method takes two optional parameters—the user secrets directory and whether or not this directory itself is optional; in other words, just ignore it if it's not there. This is illustrated in the following code snippet:

builder.AddDockerSecrets(secretsPath: "/var/lib/secrets", optional: true); 

It is possible to specify these two parameters plus an ignore prefix and a delegate for filtering out files by their names if we use the overload that takes a configuration object, as illustrated in the following code block:

builder.AddDockerSecrets(opt =>
{
    opt.SecretsDirectory = "/var/lib/secrets";
    opt.Optional = true;
    opt.IgnorePrefix = "ignore.";
    opt.IgnoreCondition = (filename) => !filename.Contains($".{env.EnvironmentName}.");
}); 

Here, we are filtering out both files starting with ignore., as well as those that do not contain the current environment name (for example, .Development.). Pretty cool!

Default providers

The ASP.NET Core code included in the default application templates (WebHostBuilder.CreateDefaultBuilder) registers the following providers:

  • JSON
  • Environment
  • Command line
  • User secrets

Of course, you can add new providers to the configuration builder to match your needs. Next, we will see how we can create a custom provider for specific configuration needs.

Creating a custom provider

Although we have several options for storing configuration values, you may have your own specific needs. For example, if you are using Windows, you might want to store your configuration settings in the Registry. For that, you need a custom provider. Let's see how we can build one.

First, you need to add the Microsoft.Win32.Registry NuGet package to your project. Then, we start by implementing IConfigurationSource, as follows:

public sealed class RegistryConfigurationSource : IConfigurationSource
{
    public RegistryHive Hive { get; set; } = RegistryHive.CurrentUser;

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new RegistryConfigurationProvider(this);
    }
}

As you can see from the preceding code block, the only configurable property is Hive, by means of which you can specify a specific Registry hive, with CurrentUser (HKEY_CURRENT_USER) being the default.

Next, we need an IConfigurationProvider implementation. Let's inherit from the ConfigurationProvider class, as this takes care of some of the basic implementations, such as reloading (which we do not support as we go directly to the source). The code can be seen here:

public sealed class RegistryConfigurationProvider : ConfigurationProvider
{
    private readonly RegistryConfigurationSource _configurationSource;

    public RegistryConfigurationProvider(
RegistryConfigurationSource configurationSource) { _configurationSource = configurationSource; } private RegistryKey GetRegistryKey(string key) { RegistryKey regKey; switch (_configurationSource.Hive) { case RegistryHive.ClassesRoot: regKey = Registry.ClassesRoot; break; case RegistryHive.CurrentConfig: regKey = Registry.CurrentConfig; break; case RegistryHive.CurrentUser: regKey = Registry.CurrentUser; break; case RegistryHive.LocalMachine: regKey = Registry.LocalMachine; break; case RegistryHive.PerformanceData: regKey = Registry.PerformanceData; break; case RegistryHive.Users: regKey = Registry.Users; break; default: throw new InvalidOperationException($"Supplied hive
{_configurationSource.Hive} is invalid."); } var parts = key.Split('\\'); var subKey = string.Join("", parts.Where(
(x, i) => i < parts.Length - 1)); return regKey.OpenSubKey(subKey); } public override bool TryGet(string key, out string value) { var regKey = this.GetRegistryKey(key); var parts = key.Split('\\'); var name = parts.Last(); var regValue = regKey.GetValue(name); value = regValue?.ToString(); return regValue != null; } public override void Set(string key, string value) { var regKey = this.GetRegistryKey(key); var parts = key.Split(''); var name = parts.Last(); regKey.SetValue(name, value); } }

This provider class leverages the Registry API to retrieve values from the Windows Registry, which, of course, will not work on non-Windows machines. The TryGet and Set methods, defined in the ConfigurationProvider class, both delegate to the private GetRegistryKey method, which retrieves a key-value pair from the Registry.

Finally, let's add a friendly extension method to make registration simpler, as follows:

public static class RegistryConfigurationExtensions
{
    public static IConfigurationBuilder AddRegistry(
this IConfigurationBuilder builder, RegistryHive hive = RegistryHive.CurrentUser) { return builder.Add(new RegistryConfigurationSource { Hive = hive }); } }

Now, you can use this provider, as follows:

builder
    .AddJsonFile("appsettings.json")
    .AddRegistry(RegistryHive.LocalMachine);

Nice and easy, don't you think? Now, let's see how we can use the configuration files for the providers that we registered.

主站蜘蛛池模板: 娱乐| 都昌县| 东城区| 广德县| 娱乐| 巴楚县| 萍乡市| 化德县| 积石山| 东港市| 芮城县| 镇巴县| 昌平区| 惠来县| 昌吉市| 丁青县| 望奎县| 柯坪县| 右玉县| 永修县| 勐海县| 林西县| 石棉县| 留坝县| 新疆| 竹北市| 霍邱县| 灌云县| 松滋市| 越西县| 格尔木市| 山阴县| 香港| 融水| 霍林郭勒市| 嫩江县| 民权县| 大渡口区| 高安市| 宿松县| 九江县|