This article focuses on the first read configuration information for the ASP. NET Core configuration tutorial, where interested partners can refer
Referring to the word "configuration", I think most of it. NET developers in mind will immediately emerge two special files, that is we are familiar with the app. Config and Web. config, for many years we have been accustomed to the structure of configuration information defined in these two files. When we get to. NET core, a lot of things that we take for granted change, including the way you define configurations. In general, the new configuration system appears to be more lightweight and more scalable, and its greatest feature is the support for diverse data sources. We can use the memory variables as the configured data source, or we can directly configure the persisted files or even the database.
Since a lot of people have never been in touch with this newly designed configuration system, in order to have a sense of this, we start with a programmatic perspective of it first. The API for configuration involves three objects, namely, configuration, Configurationbuilder, and Configurationprovider, which have corresponding interfaces to represent them. The relationship between the three objects is clear, the configuration object carries the configured information that is used during the programming process, and Configurationprovider is the provider of the original data source for the configuration information. Communication between the two is done by Configurationbuilder, which uses Configurationprovider to extract the source data and convert it to a configuration object.
First, read the configuration in the form of a key-value pair
Although the configuration information in most cases has a structured hierarchical relationship, but the "atomic" configuration items are represented in the simplest "key-value pairs", and the keys and values are strings, we will then demonstrate how to read the configuration in the form of a key-value pair using a simple example. We create a console app for ASP. NET core and add a dependency on the NuGet package for "Microsoft.Extensions.Configuration" in Project.json as follows, The configuration model is implemented in this package.
{
...
"dependencies": {
"Microsoft.Extensions.Configuration": "1.0.0-rc1-final"
},
}
Assuming that our application needs to be configured to set the date/time display format, we have defined the following Datetimeformatsettings class, which has four properties that reflect the four display formats of the DateTime object (long Date/time and short Date/time, respectively).
public class DateTimeFormatSettings
{
public string LongDatePattern {get; set;}
public string LongTimePattern {get; set;}
public string ShortDatePattern {get; set;}
public string ShortTimePattern {get; set;}
//other members
}
We want to control the date/time display format represented by the four properties of datetimeformatsettings in the form of configuration, so we define a constructor for it. As shown in the following code fragment, the constructor has an IConfiguration interface type parameter, which formally hosts the configuration object for the related config information. We call the index of the configuration object and specify the key of the corresponding config item to get its value.
public class DateTimeFormatSettings
{
//other members
public DateTimeFormatSettings (IConfiguration configuration)
{
this.LongDatePattern = configuration ["LongDatePattern"];
this.LongTimePattern = configuration ["LongTimePattern"];
this.ShortDatePattern = configuration ["ShortDatePattern"];
this.ShortTimePattern = configuration ["ShortTimePattern"];
}
}
To create a Datetimeformatsettings object that embodies the current configuration, we have to get to the configuration object that hosts the relevant config information. As we said above, the configuration object is created by Configurationbuilder, and the original config information is read by the corresponding configurationprovider. So the correct way to programmatically create a configuration object is to first create a Configurationbuilder object and then add one or more Configurationprovider objects to it. Finally, we use Configurationbuilder to create the configuration objects we need.
In accordance with the above programming pattern, we have written the following program in a console application. We have created an object of type Configurationbuilder, and calling its Add method adds Configurationprovider is an object of type Memoryconfigurationprovider. As the name implies, Memoryconfigurationprovider uses objects in memory to provide the original configuration information, specifically these original configuration information is saved in an element type of keyvaluepair<string, string> In the collection. We eventually call the build method of Configurationbuilder to get the configuration needed to create the Datetimeformatsettings object.
public class Program
{
public static void Main(string[] args)
{
Dictionary<string, string> source = new Dictionary<string, string>
{
["LongDatePattern"] = "dddd, MMMM d, yyyy",
["LongTimePattern"] = "h:mm:ss tt",
["ShortDatePattern"] = "M/d/yyyy",
["ShortTimePattern"] = "h:mm tt"
};
IConfiguration configuration = new ConfigurationBuilder()
.Add(new MemoryConfigurationProvider(source))
.Build();
DateTimeFormatSettings settings = new DateTimeFormatSettings(configuration);
Console.WriteLine("{0,-16}: {1}", "LongDatePattern", settings.LongDatePattern);
Console.WriteLine("{0,-16}: {1}", "LongTimePattern", settings.LongTimePattern);
Console.WriteLine("{0,-16}: {1}", "ShortDatePattern", settings.ShortDatePattern);
Console.WriteLine("{0,-16}: {1}", "ShortTimePattern", settings.ShortTimePattern);
}
}
To verify the relationship between the Datetimeformatsettings object created by the configuration and the configuration of the original data, we output its four properties to the console. When the program executes, it will produce the output shown below on the console, which shows that it is the true reflection of the configuration we provide.
LONGDATEPATTERN:DDDD, MMMM D, yyyy
LONGTIMEPATTERN:H:MM:SS TT
Shortdatepattern:m/d/yyyy
SHORTTIMEPATTERN:H:MM TT
Second, read the structured configuration
Most of the configurations involved in a real project have a structured hierarchy, so the configuration object in the Config model also has such a structure. A structured configuration has a tree hierarchy, and a configuration object represents a node that makes up the configuration tree, which can be represented by a config object that is the root node. Atomic configuration items that are represented as key-value pairs typically exist in a configuration object that is a leaf node, and the configuration of a non-leaf node contains a set of child nodes, and each child node is also a configuration object.
Next we also demonstrate how to define and read a configuration with a hierarchical structure as an example. We still use the previous section of the application scenario, now we need not only to set the date/time format, but also to set the format of other data types, such as the decimal type that represents the currency. For this we define the following Currencydecimalformatsettings class, whose properties digits and symbol represent the decimal place and the currency symbol, respectively. A Currencydecimalformatsettings object is still created using a configuration object that represents configurations.
{
public int Digits { get; set; }
public string Symbol { get; set; }
public CurrencyDecimalFormatSettings(IConfiguration configuration)
{
this.Digits = int.Parse(configuration["Digits"]);
this.Symbol = configuration["Symbol"];
}
}
We have defined another type named Formatsettings to represent formatting for different data types. As shown in the following code snippet, its two properties datetime and currencydecimal represent formatting for date/time and currency numbers, respectively. Formatsettings still has a constructor with a parameter type of IConfiguration interface, and its two properties are initialized in this constructor. It is important to note that the initialization of these two properties takes the "Child configuration section" of the current configuration, which is obtained by invoking the GetSection method by specifying the configuration section name.
public class FormatSettings
{
public DateTimeFormatSettings DateTime { get; set; }
public CurrencyDecimalFormatSettings CurrencyDecimal { get; set; }
public FormatSettings(IConfiguration configuration)
{
this.DateTime = new DateTimeFormatSettings(configuration.GetSection("DateTime"));
this.CurrencyDecimal = new CurrencyDecimalFormatSettings(configuration.GetSection("CurrencyDecimal"));
}
}
In the example we demonstrated above, we provide the original configuration information by using a Memoryconfigurationprovider object. Because the original configuration information is hosted by an element type of keyvaluepair<string, string>, the original configuration does not have a tree-structured hierarchy on the physical storage, So how can it ultimately provide a structured configuration object? It is very simple, although the Memoryconfigurationprovider object can only store configuration information as a simple "data dictionary", but if the path that the configuration object embodies in the config tree is the key, This data dictionary logically actually has a tree structure. In fact, this is what Memoryconfigurationprovider is doing, which is reflected in the program we have shown below.
class Program
{
static void Main(string[] args)
{
Dictionary<string, string> source = new Dictionary<string, string>
{
["Format:DateTime:LongDatePattern"] = "dddd, MMMM d, yyyy",
["Format:DateTime:LongTimePattern"] = "h:mm:ss tt",
["Format:DateTime:ShortDatePattern"] = "M/d/yyyy",
["Format:DateTime:ShortTimePattern"] = "h:mm tt",
["Format:CurrencyDecimal:Digits"] = "2",
["Format:CurrencyDecimal:Symbol"] = "$",
};
IConfiguration configuration = new ConfigurationBuilder()
.Add(new MemoryConfigurationProvider(source))
.Build();
FormatSettings settings = new FormatSettings(configuration.GetSection("Format"));
Console.WriteLine("DateTime:");
Console.WriteLine("\t{0,-16}: {1}", "LongDatePattern", settings.DateTime.LongDatePattern);
Console.WriteLine("\t{0,-16}: {1}", "LongTimePattern", settings.DateTime.LongTimePattern);
Console.WriteLine("\t{0,-16}: {1}", "ShortDatePattern", settings.DateTime.ShortDatePattern);
Console.WriteLine("\t{0,-16}: {1}\n", "ShortTimePattern", settings.DateTime.ShortTimePattern);
Console.WriteLine("CurrencyDecimal:");
Console.WriteLine("\t{0,-16}: {1}", "Digits", settings.CurrencyDecimal.Digits);
Console.WriteLine("\t{0,-16}: {1}", "Symbol", settings.CurrencyDecimal.Symbol);
}
}
As shown in the code snippet above, the Dictionary object that is used to create the Memoryconfigurationprovider object contains 6 basic configuration items, in order for them to logically have a tree-structured hierarchy, So the key actually embodies the path of the configuration section in the configuration tree where each configuration item is located, and the path is split with a colon (":"). After the program executes, the output shown below will be rendered on the console.
DateTime:
LongDatePattern : dddd, MMMM d, yyyy
LongTimePattern : h:mm:ss tt
ShortDatePattern: M/d/yyyy
ShortTimePattern: h:mm tt
CurrencyDecimal:
Digits : 2
Symbol : $
III. binding a structured configuration directly to an object
In the real project development process, we will not directly use the direct read configuration, but are inclined to like the two examples we demonstrate by creating the appropriate type (such as Datetimeformatsettings, Currencydecimalsettings and Formatsettings) to define a set of related configuration options (option), we will define the configuration options (option) for these types called option types. In the example shown above, in order to create these encapsulated configuration objects, we are in the form of manual read configuration, if there are too many defined configuration items, read-only configuration item is a very tedious task.
For an object, if we treat its property as its child node, an object also has a tree-like hierarchical structure similar to the configuration object. If we define the configuration based on the structure of an option type, or the option type is defined in turn based on the configured structure, then the attribute member of option type will have a one by one corresponding relationship with a configuration section. So in principle we can automatically bind configuration information to a specific option object.
ASP. NET core for the configured option model (Optionmodel) helps us implement bindings from configuration to option objects, and then we'll do a simple demonstration of this. The option model is implemented in the "Microsoft.Extensions.OptionModel" NuGet package, except that we need to use the option model in a way that relies on injection, so we need to add an appropriate dependency for the app as follows.
{
...
"dependencies": {
"Microsoft.Extensions.OptionsModel" : "1.0.0-rc1-final",
"Microsoft.Extensions.DependencyInjection" : "1.0.0-rc1-final"
},
}
With the option model's automatic binding mechanism, we no longer have to manually read the configuration information, so we remove the formatsettings, datetimeformatsettings, and Currencydecimalsettings constructors, Retains only its property members. In the main method as the entrance to the program, we create the Formatsettings object that represents the formatting in the following way.
class Program
{
static void Main(string[] args)
{
Dictionary<string, string> source = new Dictionary<string, string>
{
["Format:DateTime:LongDatePattern"] = "dddd, MMMM d, yyyy",
["Format:DateTime:LongTimePattern"] = "h:mm:ss tt",
["Format:DateTime:ShortDatePattern"] = "M/d/yyyy",
["Format:DateTime:ShortTimePattern"] = "h:mm tt",
["Format:CurrencyDecimal:Digits"] = "2",
["Format:CurrencyDecimal:Symbol"] = "$",
};
IConfiguration configuration = new ConfigurationBuilder()
.Add(new MemoryConfigurationProvider(source))
.Build()
.GetSection("Format"));
IOptions<FormatSettings> optionsAccessor = new ServiceCollection()
.AddOptions()
.Configure<FormatSettings>(configuration)
.BuildServiceProvider()
.GetService<IOptions<FormatSettings>>();
FormatSettings settings = optionsAccessor.Value;
Console.WriteLine("DateTime:");
Console.WriteLine("\t{0,-16}: {1}", "LongDatePattern",settings.DateTime.LongDatePattern);
Console.WriteLine("\t{0,-16}: {1}", "LongTimePattern",settings.DateTime.LongTimePattern);
Console.WriteLine("\t{0,-16}: {1}", "ShortDatePattern",settings.DateTime.ShortDatePattern);
Console.WriteLine("\t{0,-16}: {1}\n", "ShortTimePattern",settings.DateTime.ShortTimePattern);
Console.WriteLine("CurrencyDecimal:");
Console.WriteLine("\t{0,-16}: {1}", "Digits",settings.CurrencyDecimal.Digits);
Console.WriteLine("\t{0,-16}: {1}", "Symbol",settings.CurrencyDecimal.Symbol);
}
}
As shown in the preceding code fragment, we create a Servicecollection object and call the extension method AddOptions registered with the service for the option model. Next we call the Configure method to map the option type formatsettings to the corresponding configuration object. We finally use this Servicecollection object to generate a serviceprovider, and call its GetService method to get an object of type ioptions<formatsettings>, The latter's Value property returns the Formatsettings object that is bound to the associated configuration.