DotNetConfig 1.2.0

dotnet add package DotNetConfig --version 1.2.0                
NuGet\Install-Package DotNetConfig -Version 1.2.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DotNetConfig" Version="1.2.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add DotNetConfig --version 1.2.0                
#r "nuget: DotNetConfig, 1.2.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install DotNetConfig as a Cake Addin
#addin nuget:?package=DotNetConfig&version=1.2.0

// Install DotNetConfig as a Cake Tool
#tool nuget:?package=DotNetConfig&version=1.2.0                

Why

dotnet-config (or .netconfig) provides a uniform mechanism for .NET Core tools to store and read configuration values in a predictable format which can be manipulated through a command line tool, an API and also manually in any text editor by the user.

Just like git config provides a uniform way of storing settings for all git commands, the goal of dotnet-config is to foster the same level of consistency across all .NET tools. The format is (mostly) compatible with it too and therefore leverages the learnings of the git community around configuration for arbitrary tools.

What

dotnet-config provides the following:

  • A well-documented file format than can be hand-edited in any text editor.
  • A dotnet global tool to manage the configuration files (much like git config).
  • An API for dotnet tool authors to read/write settings.

By default, configuration files are named .netconfig and support four storage levels:

The files are read in the order given above, with first value found taking precedence. When multiple values are read then all values of a key from all files will be returned.

.netconfig.user can be used to keep local-only settings separate from team-wide settings in source control, and it's already a commonly ignored extension in .gitignore.

Who

The following are some of the tools that leverage .netconfig to provide flexible configuration persistence options:

dotnet-eventgrid dotnet-file dotnet-serve dotnet-vs reportgenerator sleet

Learn more about how the various tools leverage .netconfig in the Who section of our docs site.

How

Format

Example file:

# .netconfig is awesome: https://dotnetconfig.org

[serve]
	port = 8080
	gzip                    #shorthand for gzip=true

[vs "alias"]
	comexp = run|community|exp
	preexp = run|preview|exp

[file]
	# example of multi-valued variables
	url = https://github.com/dotnet/runtime/tree/master/docs/design/features
	url = https://github.com/dotnet/aspnetcore/tree/master/docs

; subsections allow grouping variables
[file "docs/design/features/code-versioning.md"]
	url = https://github.com/dotnet/runtime/blob/master/docs/design/features/code-versioning.md
	etag = 7405567...

[file "docs/APIReviewProcess.md"]
	url = https://github.com/dotnet/aspnetcore/blob/master/docs/APIReviewProcess.md
	etag = 1e4acd7...

[mytool]
    description = "\t tab and \n newline escapes, plus \\ backslash are valid"
    title = My tool is great    # internal whitespace preserved without needing double quotes
    path = C:\\tool             # backslashes always escaped, inside or outside double quotes
    size = 500kb                # numbers can have a multiplier (case insensitive) suffix kb, mb, gb, tb 
    max-size = 1T               # the 'b' is optional.
    compress = true             # multiple variants for boolean: true|false|yes|no|on|off|1|0
    secure   = yes
    localized = off
    enabled  = 1

; array like syntax for complex objects
[myArray "0"] # indecies must be unique
    description = 1st item description
    name = Fero
[myArray "1"]
    description = 2nd item description
    name = Jozo

The syntax follows closely the git-config syntax. The # and ; characters begin comments to the end of line, blank lines are ignored.

The file consists of sections and variables. A section begins with the name of the section in square brackets and continues until the next section begins. Section names are case-insensitive. Only alphanumeric characters and - are allowed in section names. Each variable must belong to some section, which means that there must be a section header before the first setting of a variable.

Sections can be further divided into subsections. To begin a subsection put its name in double quotes, separated by space from the section name, in the section header, like in the example below:

[section "subsection"]

Subsection names are case sensitive and can contain any characters except newline. Doublequote " and backslash \ can be included by escaping them as \" and \\, respectively.

Section headers cannot span multiple lines. Variables may belong directly to a section or to a given subsection. You can have [section] if you have [section "subsection"], but you don't need to.

All the other lines are recognized as setting variables, in the form name = value (or just name, which is a short-hand to say that the variable is the boolean true). Variable names are case-insensitive, allow only alphanumeric characters and -, and must start with an alphabetic character. Variables may appear multiple times; we say then that the variable is multivalued.

Leading whitespaces after name =, the remainder of the line after the first comment character # or ;, and trailing whitespaces of the line are discarded unless they are enclosed in double quotes. Internal whitespaces within the value are retained verbatim.

Backslash \ characters must always be escaped with \\. Double quotes must either be escaped with \" or be properly balanced, which causes the whitespace within to be preserved verbatim.

Beside \" and \\, only \n for newline character (NL) and \t for horizontal tabulation (HT, TAB) escape sequences are recognized.

NOTE: when using the CLI or API, these escaping rules are applied automatically

Values

Values of many variables are treated as a simple string, but there are variables that take values of specific types and there are rules as to how to spell them.

  • boolean

    When a variable is said to take a boolean value, many synonyms are accepted for true and false; these are all case-insensitive.

    • true: boolean true literals are yes, on, true, and 1. Also, a variable defined without =<value> is taken as true.

    • false: boolean false literals are no, off, false, 0 and the empty string.

  • datetime

    Variables of this type are always parsed/written using ISO 8601 (or round-trip) format.

  • number

    The value for many variables that specify various sizes can be suffixed with k, M, G or T to mean "scale the number by 1024", "by 1024x1024", "by 1024x1024x1024" or "by 1024x1024x1024x1024" respectively. The suffix is case insensitive, and can also include the b, as in kb or MB.

Array of complex objects

Creation of more complex objects in array is possible via subsections. Lets say that we have following configuration object:

public class WatchedProcess
{
    public string Name { get; set; }
    public string ApplicationPath { get; set; }
}

We would like to retrieve from our configuration as IList<WatchedProcess>. Even if git-config syntax does not have direct support for this scenario, we are able to create list of complex object with subsection and ConfigurationRoot.

ConfigurationRoot supports working with arrays by creating indices for items as "subsections". This allows us to create a section selector that picks values from the array based on the element index. For example, WatchedProcess:0:Name selects the value Name from the first item in the array. This means we can use indices as subsections and create an array of complex objects as follows:

[WatchedProcesses "0"] # indicies must be unique
	ApplicationPath = "C:\\MyProcessPath\ABCD"
	Name = NServiceBus.Host

[WatchedProcesses "1"] # indicies must be unique
	ApplicationPath = "C:\\MyProcessPath2\ABCD"
	Name = NServiceBus.Host

[WatchedProcesses "2"] # indicies must be unique
	ApplicationPath = "C:\\MyProcessPath2\ABCD"
	Name = NServiceBus.Host

With this configuration we are able to retrieve array of complex objects in following way:

var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddDotNetConfig();
var configurationRoot = configurationBuilder.Build();
var watchedProcesses = configurationRoot.GetSection(nameof(WatchedProcess)).Get<IList<Json.Appsettings.WatchedProcess>>();

NOTE: be sure that your array items have unique index

API

There are three main ways to access .netconfig values:

Native API

Version Downloads

PM> Install-Package DotNetConfig

The main usage for .NET tool authors consuming the DotNetConfig API is to first build a configuration from a specific path (will assume current directory if omitted):

var config = Config.Build();

The resulting configuration will contain the hierarchical variables set in the current directory (or the given path), all its ancestor directories, plus global and system locations.

When getting values, the supported primitives are exposed as first-class methods for Add, Get and Set, so you get quite a few usability overloads for each of Boolean, DateTime, Number and String, such as AddBoolean, GetDateTime, GetString or SetNumber:

// reads from:
// [mytool]
//   enabled = true

bool? enabled = config.GetBoolean("mytool", "enabled");

// reads from:
// [mytool.editor]
//   path = code.exe

string? path = config.GetString("mytool.editor", "path");


// reads from:
// [mytool "src/file.txt"]
//   createdOn = 2020-08-23T12:00:00Z

DateTime? created = config.GetDateTime("mytool", "src/file.txt", "createdOn");
// If value was not found, set it to the current datetime
if (created == null)
    // Would create the section if it did not previously exist, and add the variable
    config.SetDateTime("mytool", "src/file.txt", "createdOn", DateTime.Now);

Alternatively you can use the TryGetXXX methods instead, to avoid checking for null return values in cases where the variable (in the requested section and optional subsection) is not found:

if (!config.TryGetDateTime("mytool", "src/file.txt", "createdOn", out created))
    config.SetDateTime("mytool", "src/file.txt", "createdOn", DateTime.Now);

Since .netconfig supports multi-valued variables, you can retrieve all entries as ConfigEntry and inspect or manipulate them granularly:

foreach (ConfigEntry entry in config.GetAll("proxy", "url"))
{
    // entry.Level allows inspecting the location where the entry was read from
    if (entry.Level == ConfigLevel.System)
        // entry came from Environment.SpecialFolder.System
    else if (entry.Level == ConfigLevel.Global)
        // entry came from Environment.SpecialFolder.UserProfile
    else if (entry.Level == ConfigLevel.Local)
        // entry came from .netconfig.user file in the current dir or an ancestor directory
    else
        // local entry from current dir .netconfig or an ancestor directory

    Console.WriteLine(entry.GetString());
    // entry.GetBoolean(), entry.GetDateTime(), entry.GetNumber()
}

When writing values (via AddXXX or SetXXX) you can optionally specify the configuration level to use for persisting the value, by passing a ConfigLevel:

// writes on the global .netconfig in the user's profile
//[vs "alias"]
//	comexp = run|community|exp

config.AddString("vs", "alias", "comexp", "run|community|exp", ConfigLevel.Global);

You can explore the entire API in the docs site.

Microsoft.Extensions.Configuration

Version Downloads

PM> Install-Package DotNetConfig.Configuration

Usage (in this example, also chaining other providers):

var config = new ConfigurationBuilder()
    .AddJsonFile(...)
    .AddEnvironmentVariables()
    .AddIniFile(...)
    .AddDotNetConfig();

Given the following .netconfig:

[serve]
	port = 8080

[security "admin"]
    timeout = 60

You can read both values with:

string port = config["serve:port"];  // == "8080";
string timeout = config["security:admin:timeout"];  // == "60";

System.CommandLine

Version Downloads

Given the explicit goal of making .netconfig a first-class citizen among dotnet (global) tools, it offers excelent and seamless integration with System.CommandLine.

Let's asume you create a CLI app named package which manages your local cache of packages (i.e. NuGet). You might have a couple commands, like download and prune, like so:

var root = new RootCommand
{
  new Command("download")
  {
    new Argument<string>("id")
  },
  new Command("prune")
  {
    new Argument<string>("id"),
    new Option<int>("days")
  },
}.WithConfigurableDefaults("package");

The added WithConfigurableDefaults invocation means that now all arguments and options can have their default values specified in config, such as:

[package]
  id = DotNetConfig

[package "prune"]
  days = 30

Note how the id can be specified at the top level too. The integration will automatically promote configurable values to ancestor sections as long as they have compatible types (both id in download and prune commands are defined as string).

Running package -? from the command line will now pull the rendered default values from config, so you can see what will actually be used if the command is run with no values:

Usage:
  package [options] [command]

Options:
  --version       Show version information
  -?, -h, --help  Show help and usage information

Commands:
  download <id>  [default: DotNetConfig]
  prune <id>     [default: DotNetConfig]

And package prune -? would show:

Usage:
  package [options] prune [<id>]

Arguments:
  <id>  [default: DotNetConfig]

Options:
  --days <days>   [default: 30]
  -?, -h, --help  Show help and usage information

Since .netconfig supports multi-valued variables, it's great for populating default values for arguments or options that can be specified more than once. By making this simple change to the argument above:

    new Argument<string[]>("id")

We can now support a configuration like the following:

[package]
  id = DotNetConfig
  id = Moq
  id = ThisAssembly

And running the command with no id argument will now cause the handler to receive all three. You can also verify that this is the case via download -?, for example:

Usage:
  package [options] download [<id>...]

Arguments:
  <id>  [default: DotNetConfig|Moq|ThisAssembly]

Options:
  -?, -h, --help  Show help and usage information

All the types supported by System.CommandLine for multiple artity arguments and options are automatically populated: arrays, IEnumerable<T>, ICollection<T>, IList<T> and List<T>.

For numbers, the argument/option can be either long or int. Keep in mind that since numbers in .netconfig are always long, truncation may occur in the latter case.

CLI

Version Downloads

The command line tool allows you to inspect and modify configuration files used by your dotnet tools. Installation is the same as for any other dotnet tool:

> dotnet tool install -g dotnet-config

The available options and actions are (for the most part) compatible with the behavior of git config.

When reading and writing from a single file, you can for the most part just use git config along with the compatibility option -f|--file specifying the file to read/write from.

Reading and writing variables don't require any special options. The following lines first write a variable value and then retrieve its value:

> dotnet config mytool.myvariable myvalue
> dotnet config mytool.myvariable
myvalue

The value is returned verbatim via the standard output, so you can assign it directly to a variable, for example.

All current options from running dotnet config -? are:

Usage: dotnet config [options]

Location (uses all locations by default)
      --global               use global config file
      --system               use system config file
      --local                use .netconfig.user file
  -f, --file                 use given config file (git config compat)
      --path[=VALUE]         use given config file or directory

Action
      --get                  get value: name [value-regex]
      --get-all              get all values: key [value-regex]
      --get-regexp           get values for regexp: name-regex [value-regex]
      --add                  add a new variable: name value
      --unset                remove a variable: name [value-regex]
      --unset-all            remove all matches: name [value-regex]
      --set                  set value: name value [value-regex]
      --set-all              set all matches: name value [value-regex]
      --rename-section       rename section: old-name new-name
      --remove-section       remove a section: name
  -l, --list                 list all
  -e, --edit                 edit the config file in an editor

Other
      --name-only            show variable names only
      --default[=VALUE]      with --get, use default value when missing entry
      --type[=VALUE]         value is given this type, can be 'boolean', '
                               datetime' or 'number'
  -?, -h, --help             Display this help

Command line parsing is done with Mono.Options so all the following variants for arguments are supported: -flag, --flag, /flag, -flag=value, --flag=value, /flag=value, -flag:value, --flag:value, /flag:value, -flag value, --flag value, /flag value.

Sponsors

Clarius Org Kirill Osenkov MFB Technologies, Inc. Stephen Shaw Torutek DRIVE.NET, Inc. Ashley Medway Keith Pickford Thomas Bolon Kori Francis Toni Wenzel Giorgi Dalakishvili Uno Platform Dan Siegel Reuben Swartz Jacob Foshee alternate text is missing from this package README image Eric Johnson Ix Technologies B.V. David JENNI Jonathan Oleg Kyrylchuk Charley Wu Jakob Tikjøb Andersen Seann Alexander Tino Hager Mark Seemann Ken Bonny Simon Cropp agileworks-eu sorahex Zheyu Shen Vezel ChilliCream 4OTC

Sponsor this project  

Learn more about GitHub Sponsors

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (6)

Showing the top 5 NuGet packages that depend on DotNetConfig:

Package Downloads
ReportGenerator.Core

ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov, or lcov into human readable reports in various formats. The reports show the coverage quotas and also visualize which lines of your source code have been covered. Use this package if you want to write a custom plugin for ReportGenerator or if you want to call/execute ReportGenerator within your code base. Plugin development: https://github.com/danielpalme/ReportGenerator/wiki/Custom-reports https://github.com/danielpalme/ReportGenerator/wiki/Custom-history-storage

SleetLib

Access Sleet.exe commands through the Sleet API.

dotnet-config

A global tool for managing hierarchical configurations for dotnet tools, using git config format.

DotNetConfig.Configuration

DotNetConfig configuration provider implementation for Microsoft.Extensions.Configuration. Usage: var config = new ConfigurationBuilder().AddDotNetConfig().Build(); var value = config["section:subsection:variable"]); Note: section is required and subsection is optional, just like in dotnet-config.

DotNetConfig.CommandLine

Extensions to System.CommandLine to automatically read default values from .netconfig files. Usage: var root = new RootCommand { // child commands, arguments, options }.WithConfigurableDefaults(); The following heuristics are applied when providing default values: * Only arguments/options without a default value are processed. * Section matches root command name, subsection (dot-separated) for each additional nested command level (i.e. `[mytool "mycommand.myverb"]`). * Compatible arguments/options (same name/type) can be placed in ancestor section/subsection to affect default value of entire subtree. * All the types supported by System.CommandLine for multiple artity arguments and options are automatically populated: arrays, `IEnumerable{T}`, `ICollection{T}`, `IList{T}` and `List{T}`: .netconfig can provide multi-valued variables for those. * Numbers can be either `int` or `long`.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on DotNetConfig:

Repository Stars
danielpalme/ReportGenerator
ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov or lcov into human readable reports in various formats.
Version Downloads Last updated
1.2.0 17,160 7/7/2024
1.1.1 651 6/25/2024
1.1.0 158 6/25/2024
1.0.6 2,890,956 7/30/2021
1.0.5 2,108 7/30/2021
1.0.4 11,503 6/12/2021
1.0.3 3,255 4/29/2021
1.0.0-rc.3 195 4/26/2021
1.0.0-rc.2 69,187 12/21/2020
1.0.0-rc.1 1,308 12/15/2020
1.0.0-rc 112,671 9/6/2020
1.0.0-beta 2,009 9/5/2020