Last active
March 8, 2020 23:06
-
-
Save psmay/d493d7f70a5aee5d048220c06dd2b3ed to your computer and use it in GitHub Desktop.
For ConfigurationBuilder.AddInMemoryCollection(), one possibility for utilizing arbitrary <string, object> mappings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Options; | |
using Newtonsoft.Json; | |
namespace AddingStringObjectDictionaryToConfiguration | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var sectionName = "OurConfigSection"; | |
var stringObjectPairs = TestDictionary; | |
var initialData = GetInitialData(sectionName, stringObjectPairs); | |
// Build a configuration using initialData | |
var configuration = new ConfigurationBuilder() | |
.AddInMemoryCollection(initialData) | |
.Build(); | |
Console.WriteLine($"Configuration: {JsonConvert.SerializeObject(GetConfigurationAsDictionary(configuration), Formatting.Indented)}"); | |
var section = configuration.GetSection(sectionName); | |
Console.WriteLine($"Configuration section {sectionName}: {JsonConvert.SerializeObject(GetConfigurationAsDictionary(section), Formatting.Indented)}"); | |
// Use the DI extensions to map the configuration to the strongly typed options class | |
var options = GenerateOptions<MyTestOptions>(section); | |
// Look at the result | |
Console.WriteLine($"Options: {JsonConvert.SerializeObject(options, Formatting.Indented)}"); | |
} | |
private static Dictionary<string, string> GetConfigurationAsDictionary(IConfiguration configuration) | |
{ | |
return configuration.AsEnumerable(true).ToDictionary(kv => kv.Key, kv => kv.Value); | |
} | |
private static IEnumerable<KeyValuePair<string, string>> GetInitialData(string sectionName, Dictionary<string, object> stringObjectPairs) | |
{ | |
// Convert <string, object> pairs to <string, string> pairs | |
var stringStringPairs = ToStringStringPairs(stringObjectPairs); | |
// Prefix the keys with the desired section name | |
var sectionedPairs = stringStringPairs | |
.Select(kv => new KeyValuePair<string, string>($"{sectionName}:{kv.Key}", kv.Value)); | |
return sectionedPairs; | |
} | |
private static Dictionary<string, object> TestDictionary => new Dictionary<string, object>() | |
{ | |
["int"] = (int)1234, | |
["intX"] = (int?)1234, | |
["intN"] = (int?)null, | |
["long"] = (long)123456789012345L, | |
["longX"] = (long?)123456789012345L, | |
["longN"] = (long?)null, | |
["guid"] = TestGuid, | |
["guidX"] = (Guid?)TestGuid, | |
["guidN"] = (Guid?)null, | |
["dt"] = TestDateTime, | |
["dtX"] = (DateTime?)TestDateTime, | |
["dtN"] = (DateTime?)null, | |
["string"] = "abcde", | |
["stringN"] = (string)null, | |
["object"] = new object(), | |
["objectN"] = (object)null, | |
}; | |
private static Guid TestGuid => Guid.Parse("23bd8260-5c23-4d20-a09c-cad00b74c837"); | |
private static DateTime TestDateTime => new DateTime(1992, 8, 7, 6, 5, 4, DateTimeKind.Utc); | |
private static T GenerateOptions<T>(IConfiguration config) where T : class, new() | |
{ | |
var services = new ServiceCollection(); | |
services.Configure<T>(config); | |
var provider = services.BuildServiceProvider(); | |
var options = provider.GetRequiredService<IOptions<T>>().Value; | |
return options; | |
} | |
public class MyTestOptions | |
{ | |
public int Int { get; set; } | |
public int? IntX { get; set; } | |
public int? IntN { get; set; } | |
public long Long { get; set; } | |
public long? LongX { get; set; } | |
public long? LongN { get; set; } | |
public Guid Guid { get; set; } | |
public Guid? GuidX { get; set; } | |
public Guid? GuidN { get; set; } | |
public DateTime Dt { get; set; } | |
public DateTime? DtX { get; set; } | |
public DateTime? DtN { get; set; } | |
public string String { get; set; } | |
public string StringN { get; set; } | |
public object Object { get; set; } | |
public object ObjectN { get; set; } | |
} | |
public static IEnumerable<KeyValuePair<string, string>> ToStringStringPairs(IEnumerable<KeyValuePair<string, object>> source) | |
{ | |
return source | |
.Select(kv => new KeyValuePair<string, string>( | |
kv.Key, | |
ConvertToStringOrDefault(kv.Value) | |
)); | |
} | |
public static string ConvertToStringOrDefault(object value) | |
{ | |
if (TryConvertToString(value, out string stringValue, out Exception _)) | |
{ | |
return stringValue; | |
} | |
return null; | |
} | |
// Stringification can't always be reversed in a meaningful way, thus this can't convert absolutely everything | |
// into a string value that can be re-parsed. Our application has strings, numbers, datetimes, and GUIDs in the | |
// string-object dictionary, and those all appear to work. | |
// | |
// This method uses ConvertToInvariantString(), which tries, where possible, to produce values for the invariant | |
// culture instead of the current one. In the configuration binder code that converts strings back into typed | |
// values, its counterpart method ConvertFromInvariantString() is used, so this seems like the correct approach. | |
// | |
// This method produces exceptions on an out parameter in case you want your code to complain instead of | |
// defaulting when a conversion fails. | |
public static bool TryConvertToString(object value, out string stringValue, out Exception error) | |
{ | |
stringValue = null; | |
error = null; | |
if (value == null) | |
{ | |
return true; | |
} | |
// There might be a way to convince the TypeConverter to use .ToString("o") by itself, and if so there might | |
// be a way to convince the binder to use DateTime.Parse(stringValue, null, DateTimeStyles.RoundtripKind) to | |
// make the results useful. | |
if (value is DateTime dateTimeValue) | |
{ | |
stringValue = dateTimeValue.ToString("o"); | |
return true; | |
} | |
else if (value is DateTimeOffset dateTimeOffsetValue) | |
{ | |
stringValue = dateTimeOffsetValue.ToString("o"); | |
return true; | |
} | |
var converter = TypeDescriptor.GetConverter(value); | |
if (converter.CanConvertTo(typeof(string))) | |
{ | |
try | |
{ | |
stringValue = converter.ConvertToInvariantString(value); | |
return true; | |
} | |
catch (Exception e) | |
{ | |
error = new InvalidOperationException("Conversion to invariant string failed.", e); | |
return false; | |
} | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment