How to pass a POCO class to .NET Core configuration
AddInMemoryCollection
takes a collection of KeyValuePair
where a key is setting key and value is its value. This call from the question
configurationBuilder.AddInMemoryCollection("Settings", JsonConvert.SerializeObject(mySettings));
actually passes "Settings" as a key and whole JSON as one setting value, which expectedly does not work.
But the overall approach is correct, you should use AddInMemoryCollection
extension call. In collection passed to this call, setting keys are full paths within configuration, delimited by a colon. Say if you have following settings POCO:
public class SomeSettings
{
public string SomeKey1 { get; set; }
public int SomeKey2 { get; set; }
}
and it's loaded from following JSON
{
"SomeSettings": {
"SomeKey1": "SomeData",
"SomeKey2": 123
}
}
the keys would be SomeSettings:SomeKey1
and SomeSettings:SomeKey2
.
You could then add such configuration with following AddInMemoryCollection
call:
configurationBuilder.AddInMemoryCollection(new Dictionary<string, string>
{
{ "SomeSettings:SomeKey1", "SomeData" },
{ "SomeSettings:SomeKey2", 123 },
});
Now, if you want to add settings POCO with one call, you could write simple extension method that will enumerate setting class properties using reflection and return collection of key-value pairs for properties names and values.
Here is a sample:
public static class ObjectExtensions
{
public static IEnumerable<KeyValuePair<string, string>> ToKeyValuePairs(this Object settings, string settingsRoot)
{
foreach (var property in settings.GetType().GetProperties())
{
yield return new KeyValuePair<string, string>($"{settingsRoot}:{property.Name}", property.GetValue(settings).ToString());
}
}
}
public static class ConfigurationBuilderExtensions
{
public static void AddInMemoryObject(this ConfigurationBuilder configurationBuilder, object settings, string settingsRoot)
{
configurationBuilder.AddInMemoryCollection(settings.ToKeyValuePairs(settingsRoot));
}
}
In the test:
var configurationBuilder = new ConfigurationBuilder();
var mySettings = GetTestSettings();
configurationBuilder.AddInMemoryObject(mySettings, "Settings");
Such simple ObjectExtensions.ToKeyValuePairs
extension method will work for plain POCO like SomeSettings
above. However it will not work if some of the properties are also objects like here:
public class InnerSettings
{
public string SomeKey { get; set; }
}
public class OuterSettings
{
public InnerSettings InnerSettings { get; set; }
}
However I believe you got an idea and could make required adjustments if required.
I ended up with the following:
configurationBuilder.AddInMemoryCollection(ToDictionary(GetTestSettings()));
private static IEnumerable<KeyValuePair<string, string>> ToDictionary(object o) =>
AddPropsToDic(JObject.FromObject(o), new Dictionary<string, string>(), "Settings");
private static Dictionary<string, string> AddPropsToDic(JObject jo, Dictionary<string, string> dic, string prefix)
{
jo.Properties()
.Aggregate(dic, (d, jt) =>
{
var value = jt.Value;
var key = $"{prefix}:{jt.Name}";
if (value.HasValues)
return AddPropsToDic((JObject) jt.Value, dic, key);
dic.Add(key, value.ToString());
return dic;
});
return dic;
}
If you don't mind an additional dependency, I created a package Extensions.Configuration.Object just for this.
dotnet add package Extensions.Configuration.Object
And then you can do:
var configuration = new ConfigurationBuilder()
.AddObject(new // This supports anonymous and simple classes.
{
MyProperty = "MyValue",
MySection = new
{
MyOtherProperty = "MyOtherValue"
}
})
.Build();
If you don't want an additional dependency, you can just copy source code from GitHub.
There are a few problems with the given answers @the_joric and @codeFuller provided, and this is they mostly fall apart the second you put in an array, and also making sure you handle nested objects of course. @Laurynas_Lazauskas answer is actually the most correct and approached it by adding a Provider which is probably the best technical route to take. However if you are interested in a Quick and Dirty solution something like this could work, and its performance to @Laurynas_Lazauskas answer might as well be equal. The only feature I wish I could see is the ability to add in a root key.
public static Dictionary<string, string> CreateConfiguration<TObject>(this TObject @object, string rootKey = "")
where TObject : class
{
if( @object == null) { throw new ArgumentNullException(nameof(@object)); }
var d = new Dictionary<string, string>();
Dictionary<string, string> CreateConfiguration(object @obj, string key, Dictionary<string, string> dict)
{
foreach (var p in @obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (p.PropertyType.IsClass && !typeof(System.Collections.IEnumerable).IsAssignableFrom(p.PropertyType))
{
return CreateConfiguration(p.GetValue(@obj), $"{key}{(String.IsNullOrEmpty(key) ? "" : ":")}{p.Name}", dict);
}
else if(typeof(System.Collections.IEnumerable).IsAssignableFrom(p.PropertyType) && p.PropertyType != typeof(string))
{
int i = 0;
foreach( var element in (System.Collections.IEnumerable)p.GetValue(@obj))
{
d.TryAdd($"{key}{(String.IsNullOrEmpty(key) ? "" : ":")}{p.Name}:{i++}", element.ToString());
}
}
d.TryAdd($"{key}{(String.IsNullOrEmpty(key) ? "" : ":")}{p.Name}", p.GetValue(@obj).ToString());
}
return dict;
}
return CreateConfiguration(@object, rootKey, d);
}
Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
---|---|---|---|---|---|---|---|---|---|
GetConfiguration | .NET 5.0 | .NET 5.0 | 4.221 us | 0.0749 us | 0.0735 us | 0.85 | 0.02 | 1.1368 | 5 KB |
GetConfiguration | .NET 6.0 | .NET 6.0 | 3.721 us | 0.0663 us | 0.0620 us | 0.75 | 0.02 | 1.1063 | 5 KB |
GetConfiguration | .NET Core 3.1 | .NET Core 3.1 | 4.972 us | 0.0960 us | 0.0851 us | 1.00 | 0.00 | 1.1292 | 5 KB |
GetConfiguration_AddObjectNuget | .NET 5.0 | .NET 5.0 | 6.800 us | 0.1170 us | 0.0977 us | 0.78 | 0.03 | 0.8621 | 4 KB |
GetConfiguration_AddObjectNuget | .NET 6.0 | .NET 6.0 | 6.113 us | 0.1186 us | 0.1542 us | 0.71 | 0.03 | 0.7401 | 3 KB |
GetConfiguration_AddObjectNuget | .NET Core 3.1 | .NET Core 3.1 | 8.528 us | 0.1466 us | 0.3308 us | 1.00 | 0.00 | 1.0223 | 4 KB |