Skip to content

Instantly share code, notes, and snippets.

@dbones
Last active October 15, 2015 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbones/bc9f46433f2f460a1a6b to your computer and use it in GitHub Desktop.
Save dbones/bc9f46433f2f460a1a6b to your computer and use it in GitHub Desktop.
small hack, which allows you to load config from Json, Env and others into a Dynamic object
namespace ConfigBuilderTest
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
class Program
{
static void Main(string[] args)
{
string json = @"{
'email': 'james@example.com',
'active': 'true',
'createdDate': '2013-01-20T00:00:00Z',
'test': { 'name': 'test', 'address': { 'line1': '1', 'line2':'2' } },
'roles': [
'User',
'Admin'
]
}";
var c = new Config();
c.Provider<JsonLoader>().Setup(new JsonSetup {Json = json});
c.Provider<EnvironmentVariableLoader>();
//this last one is to demo the naming convention, which is converted into the object graph
c.Provider<OverrideLoader>().Setup(new OverrideSetup {Values = new [] {"active=false", "test:name=test2"}});
var settings = c.Load();
string hi = settings.Test.Name; //"test2"
}
}
public class Config
{
private IList<ILoader> _loaders = new List<ILoader>();
public INamingConvention NamingConvention { get; set; }
public Config()
{
NamingConvention = new PascalNamingConvention();
}
public TLoader Provider<TLoader>() where TLoader : ILoader, new()
{
var loader = new TLoader();
_loaders.Add(loader);
return loader;
}
public dynamic Load()
{
var config = (dynamic)new SettingNode();
foreach (var loader in _loaders)
{
loader.PopulateConfig(config, NamingConvention);
}
return config;
}
}
public interface INamingConvention
{
string GetName(string name);
}
public class PascalNamingConvention : INamingConvention
{
public string GetName(string name)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name);
}
}
public interface ILoader
{
void PopulateConfig(SettingNode config, INamingConvention convention);
}
public class VariableScope
{
public EnvironmentVariableTarget Target { get; set; }
}
public class OverrideSetup
{
public IEnumerable<string> Values { get; set; }
}
public class OverrideLoader : KvpLoader, ISetup<OverrideLoader, OverrideSetup>
{
public OverrideLoader Setup(OverrideSetup setup)
{
foreach (var entry in setup.Values)
{
_entries.Add(entry);
}
return this;
}
}
public class EnvironmentVariableLoader : KvpLoader, ISetup<EnvironmentVariableLoader,VariableScope>
{
private EnvironmentVariableTarget _target;
public EnvironmentVariableLoader()
{
_target = EnvironmentVariableTarget.Process;
}
public override void PopulateConfig(SettingNode config, INamingConvention convention)
{
foreach (DictionaryEntry variable in Environment.GetEnvironmentVariables(_target))
{
_entries.Add($"{variable.Key}={variable.Value}");
}
base.PopulateConfig(config, convention);
}
public EnvironmentVariableLoader Setup(VariableScope setup)
{
_target = setup.Target;
return this;
}
}
public abstract class KvpLoader : ILoader
{
protected readonly IList<string> _entries = new List<string>();
public virtual void PopulateConfig(SettingNode config, INamingConvention convention)
{
foreach (var entry in _entries)
{
var parts = entry.Split('=');
var key = parts[0];
SettingNode node = config;
if (key.Contains(":"))
{
var scopes = key.Split(':');
for (int i = 0; i < scopes.Length-1; i++)
{
var propertyName = convention.GetName(scopes[i]);
var temp = node[propertyName] as SettingNode;
if (temp == null)
{
temp = new SettingNode();
node[propertyName] = temp;
}
node = temp;
}
key = convention.GetName(scopes.Last());
}
node[key] = parts[1];
}
}
}
public interface ISetup<out TItem, in TOption> where TItem : ILoader
{
TItem Setup(TOption setup);
}
public class JsonSetup
{
public string Json { get; set; }
}
public class FileSetup
{
public string FileName { get; set; }
}
public class JsonLoader : ILoader, ISetup<JsonLoader, JsonSetup>, ISetup<JsonLoader,FileSetup>
{
private Func<TextReader> _json;
private bool _fromFile = false;
public JsonLoader Setup(JsonSetup setup)
{
_json = () => new StringReader(setup.Json);
return this;
}
public JsonLoader Setup(FileSetup setup)
{
_json = () =>
{
var fileStream = File.OpenRead(setup.FileName);
return new StreamReader(fileStream);
};
return this;
}
public virtual void PopulateConfig(SettingNode config, INamingConvention convention)
{
using (var sr = _json())
using (var jr = new JsonTextReader(sr))
{
var deserializer = JsonSerializer.Create();
var jobject = (JObject)deserializer.Deserialize(jr);
Populate(config, jobject, convention);
}
}
protected virtual void Populate(SettingNode config, JObject json, INamingConvention convention)
{
foreach (var property in json.Properties())
{
var name = convention.GetName(property.Name);
var type = property.Type;
if (type == JTokenType.Property)
{
type = property.Value.Type;
}
switch (type)
{
case JTokenType.Comment: break;
case JTokenType.Object:
var childConfig = (SettingNode)config[name];
if (childConfig == null)
{
childConfig = new SettingNode();
config.SetKey(name, childConfig);
}
Populate(childConfig, (JObject)property.Value, convention);
break;
case JTokenType.Array:
var childList = new ArrayList();
config.SetKey(name, childList);
Populate(childList, (JArray)property.Value, convention);
break;
default:
config[name] = ((JValue)property.Value).Value; ;
break;
}
}
}
protected virtual void Populate(ArrayList config, JArray json, INamingConvention convention)
{
foreach (var token in json.Children())
{
switch (token.Type)
{
case JTokenType.Comment: break;
case JTokenType.Object:
var childConfig = new SettingNode();
config.Add(childConfig);
Populate(childConfig, (JObject)token, convention);
break;
case JTokenType.Array:
var childArray = new ArrayList();
config.Add(childArray);
Populate(childArray, (JArray)token, convention);
break;
default:
config.Add(((JValue) token).Value);
break;
}
}
}
}
public class SettingNode : DynamicObject
{
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public void SetKey(string name, object value)
{
var key = GetKey(name);
if (_dictionary.ContainsKey(key))
{
_dictionary[key] = value;
}
else
{
_dictionary.Add(key, value);
}
}
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
string key = GetKey(binder.Name);
return _dictionary.TryGetValue(key, out result);
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
SetKey(binder.Name, value);
return true;
}
public object this[string index]
{
get
{
object result;
return _dictionary.TryGetValue(index, out result) ? result : null;
}
set
{
SetKey(index, value);
}
}
private string GetKey(string key)
{
return key;
}
public override string ToString()
{
return _dictionary.ToString();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment