Giter Site home page Giter Site logo

config's Introduction

Config.Net

NuGet Open Collective backers and sponsors GitHub Sponsors Nuget

A comprehensive, easy to use and powerful .NET configuration library, fully covered with unit tests and tested in the wild on thousands of servers and applications.

This library eliminates the problem of having configuration in different places, having to convert types between different providers, hardcoding configuration keys across the solution, depending on specific configuration source implementation. It's doing that by exposing an abstract configuration interface and providing most common implementation for configuration sources like app.config, environment variables etc.

Abstract

Quick Start

Usually developers will hardcode reading configuration values from different sources like app.config, local json file etc. For instance, consider this code example:

var clientId = ConfigurationManager.AppSettings["AuthClientId"];
var clientSecret = ConfigurationManager.AppSettings["AuthClientSecret"];

You would guess that this code is trying to read a configuration setting from the local app.config file by name and that might be true, however there are numerous problems with this approach:

  • settings are referenced by a hardcoded string name which is prone to typos and therefore crashes in runtime.
  • there is no easy way to find out where a particular setting is used in code, except for performing a fulltext search (provided that the string was not mistyped)
  • if you decide to store configuration in a different place the code must be rewritten.

Welcome to Config.Net which solves most of those problems. Let's rewrite this abomination using Config.Net approach. First, we need to define a configuration container which describes which settings are used in your application or a library:

Declare settings interface

using Config.Net;

public interface IMySettings
{
    string AuthClientId { get; }

    string AuthClientSecret { get; }
}

These interface members describe the values you are using in code and look exactly like anything else in the code. You can pass this interface around inside your application like nothing happened.

In order to instantiate this interface and bind it to application settings use ConfigurationBuilder<T> class:

IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseAppConfig()
   .Build();

This is literally all you have to do. Configuration builder is an entry to creating instances of your interface and underneath it creates a proxy class which intercepts calls to properties and fetches values from underlying configured stores.

Which Data Types are Supported?

Not all of the types can be used in the properties, because Config.Net needs to know how to convert them to and from the underlying stores. Out of the box basic .NET types (bool, double, int, long, string, TimeSpan, DateTime, Uri, Guid) are supported. Two more types are worth special mentioning:

System.Net.NetworkCredential

Is a handy built-in .NET class for holding information with username, password and domain. In reality those three fields are almost always enough to hold connection information to remote servers. The following format is understood: username:password@domain and all parts are optional.

String Arrays

Encoded using a command-line syntax:

  • values are separated by a space i.e. value1 value2
  • if you need spaces inside values you must take it in quotes i.e. "value with space" valuewithoutspace
  • quotes inside values must be escaped using a double quote ("") and the value itself should be quoted i.e. "value with ""quotes""""

It's easy to add a new type by implementing ITypeParser interface.

Using Multiple Sources

ConfigurationBuilder<T> is used to instantiate your configuration interface. You can use it to add multiple configuration sources. To get the list of sources use IntelliSense (type dot-Use):

Intellisense00

The order in which sources are added is important - Config.Net will try to read the source in the configured order and return the value from the first store where it exists.

Changing property behaviour

Option attribute can be used to annotate interface properties with extra behaviour.

Aliases

In case your property is named different to C# property name you can alias it:

public interface IMySettings
{
   [Option(Alias = "clientId")]
   string AuthClientId { get; }
}

which makes Config.Net to look for "clientId" when reading or writing.

Default values

When a property doesn't exist in any of the stores or you just haven't configured any stores at all, you will receive a default value for the property type (0 for int, null for string etc.). However, it's sometimes useful to have a different value returned as a default instead of handling that in you code. In order to do that you can use the DefaultValue property on the attribute:

public interface IMySettings
{
   [Option(Alias = "clientId", DefaultValue = "n/a")]
   string AuthClientId { get; }
}

Now when reading the value will be read as n/a instead of just null. DefaultValue property is of type object therefore the type of the value you assign to it must match the property type. If this is not the case, you will receive InvalidCastException explaining where the problem is during the .Build() stage.

However, you can set the property value to string no matter what the type is, as long as it's parseable to that type in runtime using any of the parsers.

DefaultValueAttribute

Config.Net also supports DefaultValueAttribute as an alternative to specifying default values. This allows your interfaces not to have any dependency on Config.Net library. Following definitions have the same effect:

public interface IMySettings
{
   [Option(DefaultValue = "n/a")]
   string AuthClientId { get; }
}
public interface IMySettings
{
   [DefaultValue("n/a")]
   string AuthClientId { get; }
}

Writing Settings

Some configuration stores support writing values. This can be checked by interrogating IConfigStore.CanWrite property. You can write the value back by simply setting it's value:

c.AuthClientId = "new value";

Config.Net will write the value to all configured stores that support writing. If none of the stores support writing the call will be ignored.

Of course in order for a property to be writeable you need to declare it as such in the interface:

string AuthClientId { get; set; }

Nested Interfaces

Interfaces can be nested into each other. This is useful when you have a different set of similar settings and you don't want to redeclare them, for example let's say we want to store normal and admin credentials in a configuration storage.

First, we can declare an interface to store credentials in general:

public interface ICreds
{
   string Username { get; }

   string Password { get; }
}

and contain it withing our configuration interface:

public interface IConfig
{
   ICreds Admin { get; }

   ICreds Normal { get; }
}

then, instantiate IConfig:

_config = new ConfigurationBuilder<IConfig>()
   .Use...
   .Build();

Now you can get credentials with a normal C# syntax, for instance to get admin username _config.Admin.Username etc.

All the attributes are still applicable to nested interfaces.

When getting property values, each nesting level will be separated by a dot (.) for instance admin username is fetched by key Admin.Username - something to keep in mind when using flat configuration stores.

Collections

Config.Net supports collections for primitive types and interfaces.

Collection must be always declared as IEnumerable<T> and only have a getter.

At the moment collections are read-only and writing to collections may be supported at some point in future releases.

Primitive Types

Suppose you want to read an array of integers, this can be declared as:

interface IMyConfig
{
   IEnumerable<int> Numbers { get; }
}

Interfaces

Reading an array of primitive types is not that interesting as you can always implement parsing yourself by storing some kind of a delimiter in a string i.e. 1,2,3,4 etc. Config.Net allows you to read a collection of your own complex types like so:

interface ICredentials
{
   string Username { get; }
   string Password { get; }
}

interface IMyConfig
{
   IEnumerable<ICredentials> AllCredentials { get; }
}

Limitations

Collections at the moment are only supported in read-only mode. All of the stores do support collections, however due to the nature of some underlying implementations complex collections are harder to represent and they are following the flat line syntax.

Binding To Methods

Sometimes having just setters and getters is not enough or you need to get a configuration key by name you know only at runtime. That's where dynamic configuration comes in place.

With dynamic configuration you can declare methods in configuration interface, not just properties. Have a look at this example:

public interface ICallableConfig
{
   string GetName(string keyName);
}

Calling the method will make Config.Net to read configuration using with key set to Name.value of keyName. For instance calling this method as .GetName("my_key") will return a value by key Name.my_key.

The first part of the key comes from the method name itself (any Get or Set method prefixes are removed automatically). The [Option] attribute applies here if you'd like to customise the key name i.e.

public interface ICallableConfig
{
   [Option(Alias = "CustomName")]
   string GetName(string keyName);
}

changes the key to CustomName.value of keyName.

Please take a special note that if you call a method just Get(string name) Config.Net will read settings from a root namespace i.e. Get("myprop") will return a value by key myprop. Essentially this allows you to read from the store dynamically, however you are losing the ability of performing type safe conversions.

Multiple Parameters

You can declare a method with as many parameters as you want, they will be simply chained together when deciding which key name to use, for example:

public interface ICallableConfig
{
   string GetName(string keyName, string subKeyName);
}

will read configuration with key Name.value of keyName.value of subKeyName etc.

Writing Values

The same way as you declare a method to read values, you can also declare methods for writing values. The only difference is that a method which writes values must be void. The last parameter of a writing method is considered a value parameter, for example:

public interface ICallableConfig
{
   void SetName(string keyName, string value);
}

INotifyPropertyChanged Support

INotifyPropertyChanged is part of .NET Framework and is often ised in situations when you want to monitor changes to a class' property. It is also an essential part of Xamarin, WPF, UWP, and Windows Forms data binding systems.

Config.Net totally supports INPC interface out of the box, and all you need to do is derive your interface from INPC:

public interface IMyConfiguration : INotifyPropertyChanged
{
   string Name { get; set; }
}

then build your configuration as usual and subscribe to property changed event:

IMyConfiguration config = new ConfigurationBuilder<IMyConfiguration>()
   //...
   .Build();

config.PropertyChanged += (sender, e) =>
{
   Assert.Equal("Name", e.PropertyName);
};

config.Name = "test";   //this will trigger PropertyChanged delegate

Flatline Syntax

Complex Structures

Many providers do not support nested structures. Suppose you have the following configuration declaration:

//collection element
public interface IArrayElement
{
   string Username { get; }

   string Password { get; }
}

//top level configuration
public interface IConfig
{
   IEnumerable<IArrayElement> Creds { get; }
}

and you would like to to pass two elements which in json represent as follows:

"Creds": [
   {
      "Username": "user1",
      "Password": "pass1"
   },
   {
      "Username": "user2",
      "Password":  "pass2"
   }
]

however you are using comman-line configuration provider which apparently has no nested structures. Config.Net sugggest something called a flatline syntax to be able to still use flat like providers and pass nested structures. an example above will translate to:

myapp.exe Creds.$l=2 Creds[0].Username=user1 Creds[0].Password=pass1 Creds[1].Username=user2 Creds[1].Password=pass2

which looks really expressive, however it still allows you to utilise nested structures.

In practice you probably wouldn't use command like to pass large nested structures, but rather override some of the default parameters.

Simple Structures

Simple structures can be represented by combining all the values on one single line. For instance the following configuration:

public interface ISimpleArrays
{
   IEnumerable<int> Numbers { get; }
}

can be mapped to the following command line:

myapp.exe Numbers="1 2 3"

The syntax for providing multiple values in one parameter is identical to the one described in command-line storage.

Configuration Sources

AppConfig Store

To configure the store:

IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseAppConfig()
   .Build();

It doesn't have any parameters. It's read-only, and forwards read operation to the standard ConfigurationManager class.

  • Keys are mapped straight to <appSettings> elements.
  • If a key is not found in appSettings, an attempt will be made to find it in <connectionStrings>
  • If it's still not found, and attempt will be made to find a section with a name before the first dot separator, and read the key from there.

To demonstrate this, consider the following example app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <section name="MySection" type="System.Configuration.NameValueSectionHandler"/>
   </configSections>
   <appSettings>
      <add key="AppKey" value="TestValue"/>
   </appSettings>
   <connectionStrings>
      <add name="MyConnection" connectionString="testconn"/>
   </connectionStrings>
   <MySection>
      <add key="MyKey" value="MyCustomValue"/>
   </MySection>
</configuration>

It can be mapped to configuration interface as follows:

   public interface IConfig
   {
      string AppKey { get; }

      string MyConnection { get; }

      [Option(Alias = "MySection.MyKey")]
      string MySectionKey { get; }
   }

Collections

Collections are supported by using the flatline syntax.

Command Line

This is in no way a command line framework but is rather an addition allowing you to pass configuration values explicitly on the command line.

To configure the store:

IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseCommandLine()
   .Build();

Conventions

This store will recognize any command line parameter which has a key-value delimiter in it (= or :) and optionally starts with a prefix / or - (the store trims these characters from the argument start).

If an argument has more than one delimiter the first one will be used.

Unnamed parameters

Parameters which are not named (don't have a delimiter) are skipped by default. If you wish to map a positional parameter to an option value you can specify an optional dictionary in configuration (see examples below).

Examples

Recognizable Parameters

program.exe arg1=value1 arg2:value2 arg3:value:3 -arg4:value4 --arg5:value5 /arg6:value6

all the parameters are valid and essentially will become the following:

  • arg1:value1
  • arg2:value2
  • arg3:value:3 - first delimiter used
  • arg4:value4
  • arg5:value5
  • arg6:value6
Positional parameters

In many cases command line parameters do not have a name but still need to be captured, consider this example:

myutil upload file1.txt

this is much shorter than forcing user to specify command line like

myutil /action=upload /filepath=file1.txt

You can express the configuration to capture this in the following form:

public interface IConsoleCommands
{
   [Option(DefaultValue = "download")]
   string Action { get; }

   string FilePath { get; }
}

//...


IConsoleCommands settings =
   new ConfigurationBuilder<IConsoleCommands>()
   .UseCommandLineArgs(
      new KeyValuePair<string, int>(nameof(IConsoleCommands.Action), 1),
      new KeyValuePair<string, int>(nameof(IConsoleCommands.FilePath), 2))
   .Build();

Note that the first command-line parameter starts with 1 not 0.

Collections

Command line store also supports collections by using the flatline syntax.

Environment Variables

To configure the store:

IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseEnvironmentVariables()
   .Build();

This store works with system environment variables, the ones you get on Windows cmd.exe by typing set or in PowerShell by typing Get-ChildItem Env: or on Unix base systems env.

The store supports reading and writing environment variables.

Note: Some systems like Visual Studio Team System Build replace dots (.) with underscores (_) when defining a variable. To overcome this the store will try to read the variable in both variants.

Collections

Collections are supported by using the flatline syntax.

.env files

IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseDotEnvFile()
   .Build();

Will attempt to load the .env file starting from the current directory, and going up the directory structure until it finds one. You can optionally pass folder path where the search will start from.

InMemory

To configure the store:

IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseInMemoryDictionary()
   .Build();

The store supports reading and writing, and stores configuration in the application memory. When application restarts all the setting values are lost. You may want to use this store for debugging or testing, other that that it has no real applications.

Collections

Collections are supported by using the flatline syntax.

INI

Configuring

Mapping to file
IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseIniFile(filePath)
   .Build();

This variant supports reading and writing.

Mapping to file contents
IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseIniString(contentsOfAnIniFile)
   .Build();

This variant only supports reading as you are passing full file content immediately.

Using

The store fully supports INI file sections.

In the simplest form every key in the INI file corresponds to the name of an option. For instance a definition

string MyOption { get; }

will correspond to a line in an INI file:

MyOption=my fancy value

Using Sections

A section corresponds to a part of option name before the first dot (.), for instance

[SectionOne]
MyOption=my fancy value

should use the definition

[Option(Alias = "SectionOne.MyOption")]
string MyOption { get; }
Writing

Writing is straightforward, however note that if an option has a dot in the name a section will be created by default.

Both inline and newline comments are preserved on write:

key1=value1 ;this comment is preserved
;this comments is preserved too
Edge Cases

There are a few edge cases when working with INI files you should know about:

  • A value can have an equal sign (=) and it will be considered a part of the value, because only the first equal sign is considered as a key-value separator.
  • Apparently key names cannot contain =
A note on INI comments

INI files consider semicolon (;) as an inline comment separator, therefore you cannot have it a part of a value. For instance a line like key=value; this is a comment in ideal INI implementation will be parsed out as

  • key: key
  • value: value
  • comment: comment

However, in my experience, values like secrets, connection strings etc. do often contain semicolons and in order to put them in an INI file you've got to do a trick like put a semicolon at the end of the value so that beforementioned string will become something like this key=value; this is a comment; to be parsed out as

  • key: key
  • value: value; this is a commment
  • comment: none

Although this is absolutely valid and this is how INI files should work, it is often really frustrating as when you have a lot of values with semicolons you either have to check that they do contain semicolons and add a semicolon at the end, or just get used to adding semicolon at the end of every value. I believe neither of the solutions are practical, therefore since v4.8.0 config.net does not parse inline comments by default (comment lines are still processed). This sorts out a lot of confusing questions around "why my value is not parsed correctly by config.net" or "this software is buggy" etc.

If you still want to revert to the old behavior, you can construct INI parser using the new signature:

.UseIniFile(string iniFilePath, bool parseInlineComments = false);

// or

.UseIniString<TInterface>(string iniString, bool parseInlineComments = false);

and passing true to the last argument.

  • If a value contains semicolon (;) which is a comment separator in INI files you should add it also as a last character in the value, because the parser considers only last ; as a comment separator. For example key=val;ue wil be read as val, however key=val;ue; will be read as val;ue.

Collections

Collections are supported by using the flatline syntax.

JSON

JSON is supported in read/write mode and is using System.Text.Json.Nodes namespace. For this reason it comes for free in .NET 6 and later, but will reference System.Text.Json nuget package v6 in earlier .NET versions.

JSON store does not support writing collections as of yet, mostly due to lack of time to implement it properly.

Configuring

Mapping to file
IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseJsonFile(path)
   .Build();

This variant supports reading and writing. Path can be either relative or absolute.

Mapping to file contents
IMySettings settings = new ConfigurationBuilder<IMySettings>()
   .UseJsonString(path)
   .Build();

This variant supports reading only as there is nowhere to write in this case.

Using

In the simplest form every key in the JSON file corresponds to the name of an option. For instance a definition

public interface IMySettings
{
   string AuthClientId { get; }
   string AuthClientSecreat { get; }
}

will correspond to the following JSON file:

{
   "AuthClientId": "Id",
   "AuthClientSecret": "Secret"
}
Using a setting that has a non trivial JSON Path

In a more advanced, and probably more typical scenario, the JSON setting will be nested within the configuration structure in a non trivial way (i.e., not on the root with an identical name). The Option attribute, combined with Alias property, specifies the JSON Path needed in order to reach the setting's value.

public interface IMySettings
{
   string AuthClientId { get; }
   string AuthClientSecreat { get; }
   
   [Option(Alias = "WebService.Host")]
   string ExternalWebServiceHost { get; }
}

will correspond to the following JSON file:

{
   "AuthClientId":"Id",
   "AuthClientSecret":"Secret",
   
   "WebService": {
       "Host": "http://blahblah.com:3000"
   }
}

Using With

Azure Functions

Azure function configuration can be set in portal or settings file when developing locally. This is just a pure magic and they are all exposed as environment variables at the end of the day. .UseEnvironmentVariables() will allow to read those values.

Sponsorship

This framework is free and can be used for free, open source and commercial applications. Config.Net (all code, NuGets and binaries) are under the MIT License (MIT). It's battle-tested and used by many awesome people and organisations. So hit the magic ⭐️ button, we appreciate it!!! 🙏 Thx!

The core team members, Config.Net contributors and contributors in the ecosystem do this open source work in their free time. If you use Config.Net, and you'd like us to invest more time on it, please donate. This project increases your income/productivity/usabilty too.

Why charge/sponsor for open source?

Backers

Become a backer and show your support to our open source project.

Sponsors

Does your company use Config.Net? Ask your manager or marketing team if your company would be interested in supporting our project. Support will allow the maintainers to dedicate more time for maintenance and new features for everyone. Also, your company's logo will show here - who doesn't want a little extra exposure?

Special Thanks

Thanks to JetBrains for kindly providing an open-source license to their amazing Rider IDE for Open Source Development. Rider logo

config's People

Contributors

aloneguid avatar azurecoder avatar bigman73 avatar firenero avatar flx5 avatar golavr avatar holdenmai avatar huanlin avatar jamesbondski avatar jsobell avatar kmgallahan avatar laurentm-ubi avatar magicandre1981 avatar michaelrobl avatar mikefoxnobel avatar mmbilinski avatar ryanlilla avatar tbraun-hk avatar ykarpeev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

config's Issues

Premium command-line support

At the moment it's just a weak binding in a form of param name to param value, however it would be an excellent feature to support command line fully.

That includes generating help pages, supporting positional arguments, validating parameter sets etc.

Interface hierarchy

When an interface derives from another interface, all properties should be able to be used in configuration.

Allow specifying OptionAttribute on derived class

the class has a dependency, but if the interface is moved to another project that's dependency is removed. As a result your main configuration project needs a reference, but no other projects in your solution do (I think). We'll have to check it and see.

Configuration reader for Git repo

To further separate configuration from the application bits (and their deployment), consider implementing a reader that would allow for the backing configuration data to be housed in a Git repository.

Complicating Factors:

  • Use local/installed Git client or pull in a library (e.g., LibGit2Sharp)?
  • Need to address authentication (e.g., SSH keys) and other Git client issues
  • May wish to introduce the concept of application "profiles" (e.g., folder tree in the Git repo) so that the app can be deployed more than once (using different config content from the Git repo)

ToString() returns property name not value

If I try to get for example an int, and type Console.WriteLine(Settings.PortNumber.ToString()) it writes "PortNumber" to the console, rather than the actual port number.

If I create a new variable first (int port = Settings.PortNumber;) and then call port.ToString() I get what I expect - I assume that bypasses the ToString() override, so the problem ought to be in the override somewhere.

Is anyone aware of this and working on it, or should I dig deeper?

StringArrayParser does not support Strings surrounded with hyphens

It is not possible to set string values within an string array parameter that have whitespaces (e.g. filenames /paths). Surrounding with hyphens does not help, because the StringArrayParser justs splits by whitespace.

Example:
TestArrayWSpace="testArrayA and B","testArrayC","TestArrayD or E"
will result in
testArrayA|and|B|testArrayC|TestArrayD|or|E
instead of
testArrayA and B|testArrayC|TestArrayD or E

Solution:
Due to the fact, that the commandline parser removes the hypens, additional single hyphens could be used to mark these strings. The StringArrayParser additionally needs to respect those (maybe first run a regex to extract the single-hyphend strings) (StringArrayParser.cs:21).

Example:
TestArrayWSpace="'testArrayA and B'","testArrayC","'TestArrayD or E'"

Custom Type Parser

Hello,

The readme mentions the ability to create custom type parsers by implementing ITypeParser.
Sadly I can't figure out a way to actually register the parser.
By looking at the code it doesn't seem like that is possible at all.

How manage custom sections with in AppConfig?

Hello,

Having an app.config like this one:

<configuration>
  <configSections>
    <section name="CategorizerSettings" type="???" />
  </configSections>
  <CategorizerSettings>
    <category Text="Google" Folder="GoogleFolder" />
    <category Text="Yahoo" Folder="YahooFolder" />
  </CategorizerSettings>
  <appSettings>
    <add key="SourceFolder" value="MySourceFolder" />
  </appSettings>
  ...

I can read easly app key SourceFolder, but how I can read the categories into CategorizerSettings section?

Thanks

Support group items and array style

I do not know if such a feature is available in this library, but it would be better if it were.
Data grouping
s
and in json
default

I used the array, but the result is not interesting
in json:
zz
in ini:
xxx

This will be better:
1

and json:
2

Nested options support

I want to store a lot of settings in the single file. In this case, SettingsContainer becomes a mess of huge amount of properties which is hard to maintain and use.
It would be nice to have an opportunity to create nested options. Something like this:

public class NestedConfig
    {
        public Option<string> UserId { get; } = new Option<string>(nameof(UserId), "UserId");
        public Option<string> UserName { get; } = new Option<string>(nameof(UserName), "UserName");
    }

public class AllSettings : SettingsContainer
    {
        public Option<string> AuthClientId { get; } = new Option<string>(nameof(AuthClientId), "TestId");

        public Option<string> AuthClientSecret { get; } = new Option<string>("Secret");

        public Option<int> Quantity { get; } = new Option<int>();

        public NestedConfig Nested { get; } = new NestedConfig();

        protected override void OnConfigure(IConfigConfiguration configuration)
        {
            configuration.UseJsonConfig(@"path_to_config.json");
        }
    }

Then it could be accessed in code like this:

var settings = new AllSettings();
string userId = settings.Nested.UserId;
settings.Nested.UserId.Write("test");

This will help to work with a lot of settings stored in single file and avoid naming conflicts.

Use of fields instead of Properties causes problems with interfaces

Is there any particular reason readonly fields have been chosen? Is this simply some sort of preference for reflection identification of relevant properties?
This causes a major issue in that it's not easily possible to define interfaces for configuration objects, so if you define IServerConfiguration you can only specify an interface by implicitly specifying interface members and pointing them to a backing read-only field:

public interface IServerConfiguration
{
    Option<string> HostListenAddress { get; }
    Option<string> ConnectionString { get; }
}

public class ServerConfiguration : SettingsContainer, IServerConfiguration
{
    public readonly Option<string> HostListenAddress     = new Option<string>();
    public readonly Option<string> ConnectionString     = new Option<string>();

    Option<string> IServerConfiguration.HostListenAddress     => this.HostListenAddress;
    Option<string> IServerConfiguration.ConnectionString      => this.ConnectionString;

    protected override void OnConfigure(IConfigConfiguration configuration)
    {
            configuration.UseJsonFile("./config.json");
    }
}

Is there anything stopping us from modifying the library to use properties?

Methods in nested interfaces do not return values

   public interface ICallableConfig
   {
      string GetFirst(string sectionName, string key);

      [Option(Alias = "A")]
      string GetSecond(string sectionName);

      [Option(DefaultValue = "n/a")]
      string GetThird(string name);

      void SetFirst(string name);

      INestedCallableConfig Nested { get; }
   }

   public interface INestedCallableConfig
   {
      string GetNested(string sectionName);
   }

calling _config.Nested.GetNested("section"); does not return a proper value.

Allow specifying derived classes in ConfigurationBuilder

I think you need to use new ConfigurationBuilder(), rather than the interface. I suppose you have to decide whether it's correct to dynamically create each property, or have a configuration class. A class is more flexible, as the user can add functions such derived properties or license key decryption stuff.

Drop JiraTime

The type is not used and implementation is flawed

DateTime loss of precision, miliseconds are lost in "translation"

Hi Config.net

First of all, it's been a while since I've last used this package - NICE WORK on the doc and in the usage of Interfaces - very pleased with that.

I have an issue with datetime and miliseconds getting lost.
The "u" in DateTime.ToString skips miliseconds.

I've tried adding a TypeParser supporting DateTime - tried to overwrite the existing TypeParser in Core. My own parser is used in writing but not in "reading".

I remember earlier it was possible to overwrite a TypeParser.
Lets say I would like to use "O" instead of "u" - is this possible? I know it will fault in backward compatibility - is there possible to overwrite the current format?

Let me know if I should clearify things a bit.

Entity arrays

Support entity arrays i.e. I'd like to save and load an array of interface instances.

  • read simple collections
  • read interface collections
  • documentation

Positional parameters in commandline with ':'

When specifying positional parameters in commandline and they have : in the value, like program.exe c:\path.txt parser considers : as key-value separator and doesn't extract the value.

Registration of Custom Parser

I have implemented a custom parser, but I do not see how to register it. I do not see a ContainerConfiguration in version 4.5.0.76. Was it removed? Was it added to a later version?

DoubleParser does not respect locale Settings

When running the UnitTests on a Windows-PC with german locale settings, the DoubleParser-Tests fail.

Reason

On Parsing the String-Values, there is no locale set, so the system locale is used. Due to the fact that the test-data has a point as decimal separator (for german locale a comma would be correct), the parser results in a wrong number (e.g. "1,797693" results in double value "1797693").

Problem

Changing the test-data to a german locale format with comma, will result in correct parsing the value, but the test fails anyway, because the method TypeParser.ToRawString(...) uses an invariant culture, so "1,797693" <> "1.797693".

Possible Solution 1

Always use a defined locale (invariant or en-EN, or whatever) and document that. Hard fail on other formats.

Possible Solution 2

Let the parser determine the correct locale (e.g. scanning for points and commas) -> can be tricky for special locales -> "magic" can introduce hard to find bugs.

Possible Solution 3

Passing an optional argument to the Parser-Contructor to set the locale to use for TryParse() and toRawString() -> would be always correct, but introduces complexity for the library-user.

Allow specifying file content when using INI or JSON store

Sometimes you don't have file on disk, especially when there is not disk available, however you do have a file content available. Apparently writing will not be possible, however it should be allowed to initialise a store from the file content.

Support CommandLine nakedness

Command Line Usability

If I want to supply friendly cmdline apps I might want to allow the user to omit the key part of the keyvalue pair. As an example, rather than

parq InputFilePath=/path/to/file

I might want to let my users simply enter:

parq /path/to/file

using config.NET, I am forced to force my users to enter the full key value pair, rather than defaulting the key to populate by ordinal.

Sample post-solution code

This would be rocking:

   class AppSettings : SettingsContainer
   {
      **[DefaultOrdinal(0)]** //awesomeness here
      public readonly Option<string> InputFilePath = new Option<string>();

nuget

is there any nuget package?

License missing

I love many aspects of the project. I haven't been looking more than twenty minutes, but I like the combination of not accessing via strings but properties and the fallback system with several sources - I'm not sure any other configuration system has that out of the box. Nini uses strings, YamlDotNet is more or less undocumented, and System.Configuration is just a shell to implement everything yourself on.

So, I am considering going with Config.Net, but before I delve deeper, there's an issue to resolve. I failed to find the license terms, which makes it legally difficult. Is it a regular MIT license, or did you have something else in mind?

As a side-note, I'm submitting this as an issue, because I couldn't find any discussion forum or contact method, besides a Disqus comment field on your blog, which didn't feel quite like an official channel.

Better support for saving configuration

The current support for 'saving' configuration is rather poor as it is only possible to set individual configuration values through property setters, which doesn't work nicely with configuration stores that maintain their own state and require explicit change commits:

  • It's either not possible to save configuration at all (AppSettings, database transactions) etc.
  • If commits are being done with every setting of a value (e.g. saving the INI file for each set), the overhead is massive if multiple configurations values are being set.

Would it make sense to either...

  • Introduce some kind of interface (e.g. IPersistableConfiguration) which provides a method SaveChanges, which then persists the configuration of each store through the proxy
  • Have some kind of ambient transaction / unit of work interface

Or is this a feature which is generally not desired?

Issue parsing NetworkCredential

Password ends up combination of password and domain.
Fix: to replace string s with creds in Utils class. Code and test to follow.

DateTime parser issue regarding timezone

Hi Config.net

I am writing because I have detected an issue with the default datetimeparser.
When writing datetime to file, the .ToString("u") is used. The value is stored correct.

Upon reading the stored datetime value as string, the time is not correct. Time is not correct due to timezone (z) in the stored string.

Solution could be:
A) .ToUniversalTime.ToString("u")
B) Allow supported datatypes to be replaced. Lets say I have a custom DateTimeParser I want to replace the current implementation, "RemoveParser" could come in handy.

Sample:
"Original value": 2017-07-13 22:28:52
Stored value: 2017-07-13 22:28:52Z
Read value: 2017-07-14 00:28:52

Let me know if you need further information.

Mono debugger crashes when inspecting an interface instance in the debugger

I'm currently using Rider to develop Mono-target net462 applications, and when attempting to inspect an interface instance, the debugger crashes. I'm unsure what the cause is, and I haven't been able to narrow down a cause in the interface itself. Even a simple string-typed single-property interfaces causes this behaviour.

I'm using the INI file store. I've attached the crash log from Mono.

This is under Mono 5.4.1.7, and Linux Mint 18.3. I'm using Rider, and I have the .NET Core SDK installed.

Storage.Net integration

There is some code duplication between Storage.Net (azure tables, blobs etc.) and this library, which is just stupid. I think it's better to have a writer which can accept ITableStorage or IBlobStorage. That will also automatically add any target that Storage.Net suppots now and in the future.

Default name with read-only properties

In current implementation, if I use readonly property, values are writing and then reading properly but file storage file looks awkward. For example ini storage with the following code:

    public class AllSettings : SettingsContainer
    {
        public Option<string> AuthClientId { get; } = new Option<string>("Id");

        public Option<string> AuthClientSecret { get; } = new Option<string>("Secret");

        protected override void OnConfigure(IConfigConfiguration configuration)
        {
            configuration.UseIniFile(@"path_to_config.ini");
        }
    }

Produces the following file:

<AuthClientId>k__BackingField=Id
<AuthClientSecret>k__BackingField=Secret

Similar thing happens for JSON storage:

{
    "<AuthClientId>k__BackingField":"Id",
    "<AuthClientSecret>k__BackingField":"Secret"
}

If I specify option name file is filling up correctly. It would be great to populate default name from option property correctly as well.

Current workaround for this issue is to create property like this:

public Option<string> AuthClientId { get; } = new Option<string>(nameof(AuthClientId), "TestId");

Annotation with custom serialisers

your ITypeParser is the type of thing I meant, where MyTypes.AddressSerialiser might refer to a static class that implements that interface if you're overriding the default etc. As I said, probably unnecessary for now :)

I can add another property to Option a tribute, like WithTypeParser.

UseCommandLineArgs overload

Currently there is one method accepting argument list which can accept null to use system command line parameters. Seems like null is almost always used, would make sense to have parameterless overload.

Writing configuration

I don't have any config file in my project since I like the tool to look for an existing config file first. My assumption is that the line below will create a new config file and write the default settings in it but it doesn't work. Is there a build in method to do this?

Settings = new ConfigurationBuilder<ApplicationSettings>()
                        .UseAppConfig()
                        .Build(); 

Can not debug TryParse when create a custom Parser

Hello @aloneguid and team, as title I can not debug TryParse method in custom parser. I don't sure if it is problem of my computer. I can debug ToRawString.

btw, how to verify settings when app start. I want to make sure that it will throw error if there is some mistakes, for example, if we setup setting wrong format then application should warning us before it does anything.

Here is my custom parser:

public class TimespanRangeParser : ITypeParser
    {
        public IEnumerable<Type> SupportedTypes => new[] { typeof(List<KeyValuePair<TimeSpan, TimeSpan>>) };

        public string ToRawString(object value)
        {
            if (value == null) return null;

            if (value is List<KeyValuePair<TimeSpan, TimeSpan>>)
            {
                var timeRange = value as List<KeyValuePair<TimeSpan, TimeSpan>>;
                if (timeRange != null)
                {
                    var result = string.Empty;

                    foreach (var item in timeRange)
                    {
                        result += ($"|{item.Key.Hours:00}:{item.Key.Minutes:00}:{item.Key.Seconds:00},{item.Value.Hours:00}:{item.Value.Minutes:00}:{item.Value.Seconds:00}");
                    }

                    if (result.Length > 0)
                    {
                        return result.Substring(1);
                    }
                }                
            }
            return null;
        }

        public bool TryParse(string value, Type t, out object result)
        {
            if (value == null)
            {
                result = null;
                return false;
            }

            if (t == typeof(List<KeyValuePair<TimeSpan, TimeSpan>>))
            {
                var dtRange = new List<KeyValuePair<TimeSpan, TimeSpan>>();

                var strRanges = new List<string>();
                strRanges.AddRange(value.Split('|'));
                foreach (var item in strRanges)
                {
                    var arr = item.Split(',');
                    var start = TimeSpan.Parse(arr[0]);
                    var end = TimeSpan.Parse(arr[1]);
                    dtRange.Add(new KeyValuePair<TimeSpan, TimeSpan>(start, end));
                }

                result = dtRange;
                return true;
            }

            result = null;
            return false;
        }
    }

AppSetting

<add key="TimeToRun" value="20:30:01,23:59:02|02:15:03,04:59:04" />

ConfigBuilder

public static ICrawlerSettings Settings = new ConfigurationBuilder<ICrawlerSettings>()
                .UseTypeParser(new TimespanRangeParser())
                .UseAppConfig()
                .Build();

Allow relative file paths.

Add possibility to pass relative file path into file storages like INI or JSON. This may be useful in some situations.

Read option API

Currently to read actual value from the option I need to write it in the one of the following ways:

var settings = new AllSettings();
int value1 = settings.IntegerOption;
var value2 = (int)settings.IntegerOption;

I don't like to write type or use implicit cast because I need to know actual setting type when I writing the code. And it slows down code typing speed much.

It would be useful to have a method or property to get actual value from option. Something like this:

var settings = new AllSettings();
var value = settings.IntegerOption.Value;

or

var settings = new AllSettings();
var value = settings.IntegerOption.GetValue();

Order of stores is not honoured!

The order of items added using AddStore is not honoured because the underlying container is a ConcurrentBag, which is unordered, so the enumerator does not return the values in the order they were added, and store priorities are ignored.
This is a major issue if you're using more than one store!

Reusable interfaces

It would be great to have interfaces as getter properties in configuration interfaces themselves. It will allow some sort of reusability when configuration sections have an identical structure i.e.

interface ICredentials
{
  string Username {get;}
  string Password {get;}
}

interface IUserCreds
{
  ICredentials NormalCreds { get; }
  ICredentials AdminCreds { get; }
}
``

.INI provider comments

.INI provider should have a settings "comments allowed" which when false can ignore the ';' character anywhere in the string.

config value issue

Weird thing, can't read a '0.0.0.0' value

e.g.

ControllerServerIP=0.0.0.0

Please fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.