Giter VIP home page Giter VIP logo

system.configuration.abstractions's Introduction

System.Configuration.Abstractions Build status

  • Introduction
  • Installation
  • Getting started
  • Features
    • Generic typed helper methods for retrieving typed config values
    • Configuration Interceptors
    • Configuration Substitution Interceptor
  • Contributing
  • Credits

In most projects, you're going to have some configuration. In .NET projects, it'll probably start in your app.config or web.config file.

However, if you love TDD, you'll likely have notice that all of the built in configuration classes are horribly un-testable. They all revolve around static references to System.Configuration.ConfigurationManager, and don't really have any interfaces, so in every project, you end up wrapping them into something like "IAppSettingsWrapper", in order to write tests.

After writing these wrappers several thousand times, and being inspired by the excellent "System.IO.Abstractions" package, we've put together a standardised set of wrappers around these core framework classes.

If you want

  • to mock/stub/whatever out your App/Web.config files
  • to assert that the values from configuration are really configuring your application
  • to add custom hooks around loading configuration values
  • stronger typing

The this is for you.

Installation

Via NuGet:

	PM> Install-Package System.Configuration.Abstractions

Getting started

The simplest use case is to bind up IConfigurationManager to System.Configuration.Abstractions.ConfigurationManager in your DI container. Alternatively, you can use System.Configuration.Abstractions.ConfigurationManager.Instance - a property that'll new up a new instance of IConfigurationManager each time it's accessed.

If you want to directly switch out calls to System.Configuration.ConfigurationManager in-place, to take advantage of the strongly typed extensions and IConfigurationInterceptors you can replace calls to System.Configuration.ConfigurationManager with calls to System.Configuration.Abstractions.ConfigurationManager.Instance in-line, and your code should function identically.

Lastly, you can just new up an instance of System.Configuration.Abstractions.ConfigurationManager anywhere, using its default constructor, and everything'll be just fine.

Examples:

    // Usages

    // You can use the singleton
    var valString = ConfigurationManager.Instance.AppSettings["stringKey"];
    var valInt = ConfigurationManager.Instance.AppSettings.AppSetting<int>("intKey");

    // You can new up an instance
    var configMgr = new ConfigurationManager();
    var valString2 = configMgr.AppSettings["stringKey"];
    var valInt2 = configMgr.AppSettings.AppSetting<int>("intKey");

    // You can new up an instance with configuration values
    var configMgr3 = new ConfigurationManager(new NameValueCollection {{"stringKey", "hello"}, {"intKey", "123"}});
    var valString3 = configMgr3.AppSettings["stringKey"];
    var valInt3 = configMgr3.AppSettings.AppSetting<int>("intKey");

    // You can just switch out calls in place for backwards compatible behaviour
    var old = System.Configuration.ConfigurationManager.AppSettings["stringKey"];
    var @new = ConfigurationManager.Instance.AppSettings["stringKey"];

    // You can wire up to your container
    ninjectContainer.Bind<IConfigurationManager>().ToMethod(()=> return new ConfigurationManager());

Features

Generic typed helper methods for retrieving typed config values

The IAppSettingsExtended interface, which our AppSettingsExtended class implements, contains four new methods:

  • string AppSetting(string key, Func<string> whenKeyNotFoundInsteadOfThrowingDefaultException = null);
  • T AppSetting<T>(string key, Func<T> whenKeyNotFoundInsteadOfThrowingDefaultException = null);
  • T AppSettingConvert<T>(string key, Func<T> whenConversionFailsInsteadOfThrowingDefaultException = null);
  • T AppSettingSilent<T>(string key, Func<T> insteadOfThrowingDefaultException = null);

These strongly typed "AppSetting" helpers, will convert any primitive types that Convert.ChangeType supports. The most obvious use case being int / bool / float / int? / bool? from their string representations - keeping alot of noisy conversions out of your code. You can also provide an optional Func which will get invoked if the key you're requesting is not found - otherwise, we'll throw an exception.

The following usage examples illustrate how to use these helpers:

    // Before *******************************

    var settingThatIsAnInteger = System.Configuration.ConfigurationManager.AppSettings["key"];
    int someInt;
    if (Int32.TryParse(settingThatIsAnInteger, out someInt))
    {
        someInt = 123; // Default
    }

    var settingThatIsABool = System.Configuration.ConfigurationManager.AppSettings["otherKey"];
    bool someBool;
    if (bool.TryParse(settingThatIsAnInteger, out someBool))
    {
        someBool = true; // Default
    }

	bool? someBool;
	try
	{
		var settingThatIsABool = System.Configuration.ConfigurationManager.AppSettings["otherKey"];
		if (bool.TryParse(settingThatIsAnInteger, out someBool))
		{
			someBool = true; // Default
		}
	}
	catch
	{
		someBool = true;
	}

    // After ********************************

    var withADefault = ConfigurationManager.Instance.AppSettings.AppSetting("key", () => 123);
    var withoutADefault = ConfigurationManager.Instance.AppSettings.AppSetting<int>("key");

    var worksWithAllPrimatives = ConfigurationManager.Instance.AppSettings.AppSetting<bool>("otherKey");
    var worksWithNullables = ConfigurationManager.Instance.AppSettings.AppSetting<bool?>("otherKey");

    var customNotFoundHandler = ConfigurationManager.Instance.AppSettings.AppSetting<bool?>("otherKey", () =>
    {
        throw new MyCustomMissingKeyException();
    });

    var customNotFoundHandler = ConfigurationManager.Instance.AppSettings.AppSetting<bool?>("otherKey", () =>
    {
        throw new MyCustomMissingKeyException();
    },
	() =>
    {
        throw new MyCustomConversionException();
    });

    var customNotFoundHandler = configurationManagerExtended.AppSettingConvert<bool?>("otherKey", () =>
    {
        throw new MyCustomConversionException();
    });

	var defaultValue = true;
	var silentHandler = configurationManagerExtended.AppSettingSilent<bool>("otherKey", () =>
    {
		// Log Warning
		// ...

		// Return Default
        return defaultValue;
    });

IConfigurationInterceptors

IConfigurationInterceptor's are hooks that, if registered, allow you to intercept and manipulate the values retrieved from configuration. IConnectionStringInterceptor's are hooks that allow you to manipulate connection strings during retrieval in a similar manner.

To wire up an IConfigurationInterceptor or IConnectionStringInterceptor, first, implement one, then call the static method ConfigurationManager.RegisterInterceptors(interceptor); Your interceptors are singletons and should be thread safe as the same instance could be called across multiple threads concurrently.

Example:

	ConfigurationManager.RegisterInterceptors(new ConfigurationSubstitutionInterceptor());
	var result = ConfigurationManager.Instance.AppSettings.AppSetting<string>("key"); // Interceptor executes
	var result2 = ConfigurationManager.Instance.AppSettings["key"]; // Interceptor executes

Interceptors fire for both the AppSetting helper, and the standard NameValueCollection methods and indexers. If you want to by-pass interception, access the "Raw" property for the original collection. This is a change in behaviour in V2.

Why would I want interceptors?

An obvious example would be the presence of an appSetting looking like this:

    <add key="my-key" value="{machineName}-something" />

You could easily add an interceptor to detect and fill in {machineName} from an environmental variable, keeping your configuration free of painful transformations. There are several other useful scenarios (auditing and logging, substitution, multi-tenancy) that interceptors could be useful in.

Included Interceptors

ConfigurationSubstitutionInterceptor

The ConfigurationSubstitutionInterceptor is bundled with the package, firstly as an example, but also as a useful configuration interceptor. It supports embedding any appsetting into any other. Given:

    <add key="key1" value="valueOfOne" />
    <add key="key2" value="{key1}-valueOfTwo" />

With this interceptor registered, this is true:

	var result = ConfigurationManager.AppSetting<string>("key2");
	Console.WriteLine(result); // writes: valueOfOne-valueOfTwo

This interceptor will help you simplify transformed web or app config files that rely on similar / repetitive token replacements, by allowing you to override just one value, and have it nested across the rest of your configuration using the interceptor.

ITypeConverters

Wired up just like IConfigurationInterceptors - TypeConverters let you specify custom type conversion logic. We include one TypeConverter by default - that converters to Uri. To implement your own type converter, you need to implement the following interface:

public interface IConvertType
{
	Type TargetType { get; }
	object Convert(string configurationValue);
}

and register your converter by using

var converter = new UserConverterExample();
ConfigurationManager.RegisterTypeConverters(converter);

Your type converter will then be invoked whenever you request a mapping to the type that your converter supports.

Contributing

Send a pull request with a passing test for any bugs or interesting extension ideas.

Credits

David Whitney

system.configuration.abstractions's People

Contributors

davidwhitney avatar jakejgordon avatar sachpatel 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

system.configuration.abstractions's Issues

Guid Parsing

The following doesn't work:

ConfigurationManager.Instance.AppSettings.AppSetting<Guid>("someGuidVal", () => null)

Just can't parse it. What's the option here? I can't tell from your documentation ..

Uri Converter

Dave,

I find myself converting Uri entries in the config from strings to 'new Uri(string)' so hoping to perform this naturally within the 'System.Configuration.Abstractions.AppSetting()' method with a simple;

            if (typeof(T) == typeof(Uri))
                return (T) (object) (new Uri(rawSetting));

Rather than going down the path of supplying a list of Custom Converters, etc., what are you views?

Cheers,

Billy

"Source array was not long enough. Check srcIndex and length, and the array's lower bounds"

I'm using custom converters to translate string values to an enum. I've noticed that every now and then I get the exception mentioned in the issue title. Most of the time, the code works just fine, and I'm wondering if it has to do with too many simultaneous requests.

I'm using v2.0.2.26.

Here's the stack trace when the exception happens in an ashx implementing IHttpHandler:

Stack Trace:
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
   at System.Collections.Generic.List`1.set_Capacity(Int32 value)
   at System.Collections.Generic.List`1.EnsureCapacity(Int32 min)
   at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
   at System.Configuration.Abstractions.ConfigurationManager.RegisterTypeConverters(IConvertType[] converters) in c:\projects\system-configuration-abstractions\System.Configuration.Abstractions\ConfigurationManager.cs:line 38
   at project.name.Callback.ProcessRequest(HttpContext context) in C:\Users\username\Documents\working\library.name\project.name\Callback.ashx.cs:line 32
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

the usage in the ashx is:

        public void ProcessRequest(HttpContext context)
        {
            var wdsLogging= new Logging(13);
            var form = HttpContext.Current.Request.Form;
            
            var ourPlatformEnumConverter = new OurPlatformEnumConverter();
            var vendorPlatformEnumConverter = new vendorPlatformEnumConverter();
            ConfigurationManager.RegisterTypeConverters(ourPlatformEnumConverter, vendorPlatformEnumConverter );

and I've also had it happen every now and then in a webform page as well.

Do you have any thoughts on what might be causing it or how to fix it?

Mocking out configuration section

I use IConfigurationManager to mock configuration, but I found no easy way to mock values stored in configuration sections.

For example, I want to change session timeout during test.

I get session timeout from web.config in following way:

  SessionStateSection section =  (SessionStateSection)_configurationManager.GetSection("system.web/sessionState");
  sessionTimeout = TimeSpan.FromMinutes((int)section.Timeout.TotalMinutes);

where _configurationManager is instance of IConfigurationManager.

How can I set values for "system.web/sessionState" section using System.Configuration.Abstractions?

Conversion to enum seems to fail after each redeploy + JITer warmup

Hello, I'm using custom converters to map from a string value in the config to an enum. For the most part, everything has been working great, but I noticed via logs that every once in a while, I'd get an InvalidCastException. This is in a *.ashx web handler acting as a web service (old code base), so I'd hit it once, it'd throw the exception, I'd hit it again right away without changes, and it'd work perfectly.

I've narrowed this down: the first time I deploy the webservice and hit it, the JITer has to spin everything up, and I get a delay and then a InvalidCastException on my enum. If I immediately send another request via Postman, it works perfectly and immediately. I've hammered the webservice and the only time it fails is the first time after a deploy.

Is there some kind of timeout used by the library or you can you think of any reasons / fixes for this behavior? In case it's relevant, code snippets follow. Thank you.

    public enum OurPlatforms
    {
        Dev,
        Production
    }
    public class OurPlatformEnumConverter : IConvertType
    {
        public object Convert(string configurationValue)
        {
            if (configurationValue == OurPlatforms.Dev.ToString())
            {
                return OurPlatforms.Dev;
            }
            else if (configurationValue == OurPlatforms.Production.ToString())
            {
                return OurPlatforms.Production;
            }
        }
        public Type TargetType => typeof(OurPlatforms);

I'm using them in a config factory, which gets wired up like this in the factory constructor:

        public ProcessorConfigFactory()
        {
            var ourPlatformEnumConverter = new OurPlatformEnumConverter();
            ConfigurationManager.RegisterTypeConverters(ourPlatformEnumConverter);
        }

and then it reads out the config value that should map out to an enum when a Get method is called:

        private OurPlatforms GetOurPlatform(IConfigurationManager manager)
        {
            return manager.AppSettings.AppSettingConvert<OurPlatforms>("SomeNamespace.OurPlatform");
        }

Getting error Could not load file or assembly

InnerException {"Could not load file or assembly 'System.Configuration.Abstractions.NetStandard, Version=2.0.2.45, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.":"System.Configuration.Abstractions.NetStandard, Version=2.0.2.45, Culture=neutral, PublicKeyToken=null"} System.Exception {System.IO.FileNotFoundException}

Tried adding



It doesn't work

Why is the PublicKeyToken null?

Reading appsettings.json in .net core?

I've successfully used the library in other projects and have now begun a new API project in .Net Core 2.x targeting the framework 4.6.1 where I'm reusing some of the previous functionality.

Note that I'm not very familiar with .net core yet so it could be that I'm doing something silly. I'm getting a "Calling code requested setting named 'Vendor.OurPlatform' but it was not in the config file" exception. I'm using v2.0.2.45 of Abstractions.

My appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {},
  "Vendor.OurPlatform": "AzureDev",
  "Vendor.VendorPlatform":  "Demo"
}

and it validates as proper json.

I have the following function:

        private OurPlatforms GetOurPlatform(IConfigurationManager manager)
        {
            return manager.AppSettings.AppSettingConvert<OurPlatforms>("Vendor.OurPlatform");
        }

As far as I can tell, the keys match (because I pasted them straight out of the web config of a project where this all works) and they're on the proper level in the json object.

Am I missing something obvious? I've tried about every combination of things I can think of with variations on the json structure, putting it in other files, and even reading up on ConfigurationManager. This issue here (Azure/azure-functions-core-tools#33) seems to suggest that .net's ConfigurationManager can indeed read the appsettings.json file...

Though Core has its own configuration stuff, I'd prefer to keep using System.Configuration.Abstractions for this project due to a shared library...

Thanks.

Exception

Hmm, I'm trying to use this in a service project being deployed to many computers and I'm unable to get it running anywhere but my machine. This is the exception I'm getting out in the wild:

Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Configuration.ConfigurationErrorsException
Stack:
   at System.Configuration.Abstractions.AppSettingsExtended.AppSetting[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.String, System.Func`1<System.__Canon>)
   at Automator.Program.Main()

Any ideas?

Mixed use of .NET and System.Configuration.Abstractions ConfigurationManager causes inconsistent behavior

When using a mix of the .NET System.Configuration.ConfigurationManager and System.Configuration.Abstractions.ConfigurationManager to retrieve and update appSettings, the returned results aren't consistent.

Here are the full steps to reproduce the problem:

Setup an App.config that contains:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="something" value="A" />
  </appSettings>
</configuration>

Create a System.Configuration.Abstractions.ConfigurationManager instance and load the app setting:

IConfigurationManager config = new ConfigurationManager();
var firstValue = config.AppSettings["something"]; // This returns "A"

Now use a .NET System.Configuration.ConfigurationManager to update the value.

var testConfig = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
testConfig.AppSettings.Settings["something"].Value = "B";
testConfig.Save();

Then try to refresh the section and retrieve the update value using the System.Configuration.Abstractions.ConfigurationManager.

config.RefreshSection("appSettings");
var secondValue = config.AppSettings["something"]; // This returns "A" but _should_ return "B".

The above code should have returned the updated value "B" but returns the original value "A".
I've run the above scenario and confirmed the value in App.config file does get updated but I suspect the System.Configuration.Abstractions.ConfigurationManager is using a memory backed cache of app settings.

I'm not sure of the correct solution to this bug other than possible not mixing usage of System.Configuration.Abstractions.ConfigurationManager and the .NET System.Configuration.ConfigurationManager

License file is missing from the repo

Hello devs,

This is really a request and not an issue. I am trying to use your library for our organization but there is no LICENSE file present under the GitHub repo. I know your library is covered by MIT license and per their template https://opensource.org/licenses/MIT you may create a license file by filling out the specific info. May I please request you to create one so that we could use your wonderful library within our application legally?

In case the license file already exists somewhere, would you please provide me with its location?

Thank you,
Faisal

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.