using System; using System.Dynamic; using System.Reflection; using System.Runtime.CompilerServices; using Newtonsoft.Json; [assembly: InternalsVisibleTo("deployment.tests")] namespace greyn.Deployment { public static class ConfigurationBootstrapper { private const string confpath = "appsettings.json"; public static T Load() where T : new() { if (File.Exists("appsettings.json")) { /* "aaaahhh this is the most abstract and reflective thing in the goddamn world aaaahhhhh next you'll make an ECS on a dynamic aaaahhh" * Ok, yes. I share these fears. but. * this way we can read a *very* arbitrary configuration file. * some random external file's configuration values is a good reason to aim for very high flexibility, imo * if the configuration expects new values we write them in, * if you left other junk for whatever reason, we don't delete them. */ var actualConfig = JsonConvert.DeserializeObject(File.ReadAllText(confpath)) ?? new ExpandoObject(); var toReturn = new T(); populateExpando(toReturn, ref actualConfig); File.WriteAllText(confpath, JsonConvert.SerializeObject(actualConfig, Formatting.Indented)); readFromExpando(ref toReturn, actualConfig); return toReturn; } else { var toReturn = new T(); File.WriteAllText(confpath, JsonConvert.SerializeObject(toReturn)); return toReturn; } } //TODO: make private but get tests to cooperate... I think that's a c# limitation internal static void populateExpando(T config, ref ExpandoObject fromFile) { if (config == null) return; Console.WriteLine("config is not null"); Console.WriteLine($"fromfile null: {fromFile == null}"); if (fromFile == null) { var expandoPopulated = new ExpandoObject(); var dictionaryFromExpandoPopulated = (IDictionary)expandoPopulated; foreach (var memberInfo in config.GetType().GetMembers()) { switch (memberInfo.MemberType) { case MemberTypes.Field: dictionaryFromExpandoPopulated.Add(memberInfo.Name, ((FieldInfo)memberInfo).GetValue(config)); break; case MemberTypes.Property: dictionaryFromExpandoPopulated.Add(memberInfo.Name, ((PropertyInfo)memberInfo).GetValue(config)); break; default: break; } } expandoPopulated = (ExpandoObject)dictionaryFromExpandoPopulated; fromFile = expandoPopulated; return; } var dictionaryFromExpandoFromFile = (IDictionary)fromFile; foreach (var memberInfo in config.GetType().GetMembers(BindingFlags.DeclaredOnly)) { Console.WriteLine($"checking property: {memberInfo.Name}"); if (dictionaryFromExpandoFromFile.TryGetValue(memberInfo.Name, out object? valueFromDict)) { Console.WriteLine($"dictionary has it - {valueFromDict}"); if (valueFromDict != null) { Console.WriteLine($"value from configuration value is not null"); switch (memberInfo.MemberType) { case MemberTypes.Field: var asField = (FieldInfo)memberInfo; if ((asField.FieldType.GetMembers(BindingFlags.DeclaredOnly)?.Count() ?? 0) > 0) { Console.WriteLine($"{asField.Name} (as field) has {asField.FieldType.GetMembers(BindingFlags.DeclaredOnly) .Count()} members."); var childMember = (ExpandoObject)dictionaryFromExpandoFromFile[asField.Name]; // (ExpandoObject)asField.GetValue(fromFile); populateExpando(asField.GetValue(config), ref childMember); asField.SetValue(fromFile, childMember); } break; case MemberTypes.Property: var asProperty = (PropertyInfo)memberInfo; if ((asProperty.PropertyType.GetMembers(BindingFlags.DeclaredOnly)?.Count() ?? 0) > 0) { Console.WriteLine($"{asProperty.Name} (as property) has {asProperty.PropertyType.GetMembers(BindingFlags.DeclaredOnly) .Count()} members."); var childMember = (ExpandoObject)dictionaryFromExpandoFromFile[asProperty.Name]; // (ExpandoObject)asProperty.GetValue(fromFile); populateExpando(asProperty.GetValue(config), ref childMember); asProperty.SetValue(fromFile, childMember); } break; default: Console.WriteLine($"membertype unhanlded. {memberInfo.MemberType}"); break; } } } else { Console.WriteLine($"dictionary didn't have it. so let's add to fromFile"); switch (memberInfo.MemberType) { case MemberTypes.Field: var addTry = fromFile.TryAdd(memberInfo.Name, ((FieldInfo)memberInfo).GetValue(config)); Console.WriteLine($"ostensibly, succeeded? {addTry}"); break; case MemberTypes.Property: var addTry2 = fromFile.TryAdd(memberInfo.Name, ((PropertyInfo)memberInfo).GetValue(config)); Console.WriteLine($"ostensibly, succeeded? {addTry2}"); break; default: break; } } Console.WriteLine($"{memberInfo.Name} handled"); } } internal static void readFromExpando(ref T config, ExpandoObject readFromFile) { //TODO: read from Expando } // private static T sync(ref T config, ref ExpandoObject readFromFile) // { // var fieldsFoundThisLevel = new List(); // if(config == null) // { // //TODO: it's entirely possible this is null. // throw new ArgumentNullException(); // } // if(readFromFile == null) // { // //TODO: it's entirely possible this is null. // throw new ArgumentNullException(); // } // var readUnExpandoed = (IDictionary)readFromFile; // //if it's present on what was read // //set it on Config // //if it's not found on what was read, or type is different // foreach (var prop in config.GetType().GetProperties()) // { // fieldsFoundThisLevel.Add(prop.Name); // if(readUnExpandoed.TryGetValue(prop.Name, out object? valueFromFile)) //so a configuration file can *set* a value *to null* // { // if(valueFromFile != null) // { // var defaultConfigValue = prop.GetValue(config); // if (defaultConfigValue != null) // { // //expandofy, since we objectified it. // var expandoFromFile = new ExpandoObject(); // var dictionaryFromExpandoFromFile = (IDictionary)expandoFromFile; // foreach (var property in valueFromFile.GetType().GetProperties()) // dictionaryFromExpandoFromFile.Add(property.Name, property.GetValue(valueFromFile)); // expandoFromFile = (ExpandoObject)dictionaryFromExpandoFromFile; // //dive in, repeat // sync(ref defaultConfigValue, ref expandoFromFile); // //we've now Sync'd expandoFromFile and defaultConfigValue // prop.SetValue(readFromFile, expandoFromFile); // prop.SetValue(config, expandoFromFile); // } // } // } // else // { // readFromFile.TryAdd(prop.Name, prop.GetValue(config)); // } // } // } } }